summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp17
-rw-r--r--Android.bp28
-rw-r--r--Ravenwood.bp2
-rw-r--r--SECURITY_STATE_OWNERS5
-rw-r--r--apct-tests/perftests/healthconnect/Android.bp44
-rw-r--r--apct-tests/perftests/healthconnect/AndroidManifest.xml26
-rw-r--r--apct-tests/perftests/healthconnect/AndroidTest.xml61
-rw-r--r--apct-tests/perftests/healthconnect/OWNERS6
-rw-r--r--apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt54
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java107
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java71
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java10
-rw-r--r--api/Android.bp4
-rw-r--r--core/api/current.txt41
-rw-r--r--core/api/system-current.txt71
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/ActivityThread.java11
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/Notification.java7
-rw-r--r--core/java/android/app/NotificationManager.java13
-rw-r--r--core/java/android/app/OWNERS4
-rw-r--r--core/java/android/app/WindowConfiguration.java7
-rw-r--r--core/java/android/app/usage/StorageStats.java77
-rw-r--r--core/java/android/app/usage/flags.aconfig7
-rw-r--r--core/java/android/appwidget/flags.aconfig9
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java18
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraConfig.java40
-rw-r--r--core/java/android/content/pm/PackageStats.java39
-rw-r--r--core/java/android/content/pm/SigningDetails.java3
-rw-r--r--core/java/android/content/pm/SigningInfo.java6
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/hardware/SystemSensorManager.java9
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java6
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java17
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java4
-rw-r--r--core/java/android/nfc/flags.aconfig7
-rw-r--r--core/java/android/os/OWNERS3
-rw-r--r--core/java/android/os/Process.java3
-rw-r--r--core/java/android/os/ServiceSpecificException.java2
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/provider/Telephony.java10
-rw-r--r--core/java/android/security/flags.aconfig8
-rw-r--r--core/java/android/view/InsetsSource.java4
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java4
-rw-r--r--core/java/android/view/InsetsSourceControl.java6
-rw-r--r--core/java/android/view/SurfaceControl.java95
-rw-r--r--core/java/android/view/View.java40
-rw-r--r--core/java/android/view/ViewRootImpl.java45
-rw-r--r--core/java/android/view/Window.java36
-rw-r--r--core/java/android/view/WindowInsets.java5
-rw-r--r--core/java/android/view/WindowManager.java50
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java35
-rw-r--r--core/java/android/view/animation/AnimationUtils.java15
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java9
-rw-r--r--core/java/android/webkit/URLUtil.java97
-rw-r--r--core/java/android/webkit/WebSettings.java8
-rw-r--r--core/java/android/window/TransitionInfo.java3
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java29
-rw-r--r--core/java/com/android/internal/pm/parsing/IPackageCacher.java (renamed from tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java)24
-rw-r--r--core/java/com/android/internal/pm/parsing/PackageParser2.java (renamed from services/core/java/com/android/server/pm/parsing/PackageParser2.java)77
-rw-r--r--core/jni/android_hardware_SensorManager.cpp16
-rw-r--r--core/proto/android/providers/settings/global.proto5
-rw-r--r--core/proto/android/view/insetssource.proto2
-rw-r--r--core/proto/android/view/insetssourceconsumer.proto3
-rw-r--r--core/proto/android/view/insetssourcecontrol.proto3
-rw-r--r--core/res/res/values/attrs_manifest.xml7
-rw-r--r--core/res/res/values/config.xml7
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/companiontests/src/android/companion/SystemDataTransportTest.java54
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java24
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt18
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java33
-rw-r--r--core/tests/overlaytests/Android.mk15
-rw-r--r--core/tests/overlaytests/host/Android.mk19
-rw-r--r--core/tests/overlaytests/host/test-apps/Android.mk16
-rw-r--r--core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp57
-rw-r--r--core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk56
-rw-r--r--core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp97
-rw-r--r--core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk93
-rw-r--r--data/etc/platform.xml6
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java96
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java30
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java120
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java5
-rw-r--r--location/java/android/location/LocationResult.java78
-rw-r--r--location/java/android/location/flags/gnss.aconfig14
-rw-r--r--media/java/android/media/AudioManager.java66
-rw-r--r--media/java/android/media/FadeManagerConfiguration.java15
-rw-r--r--media/java/android/media/IAudioService.aidl21
-rw-r--r--media/java/android/media/IMediaRouter2.aidl4
-rw-r--r--media/java/android/media/IMediaRouterService.aidl10
-rw-r--r--media/java/android/media/MediaRoute2Info.java145
-rw-r--r--media/java/android/media/MediaRouter2.java151
-rw-r--r--media/java/android/media/MediaRouter2Manager.java53
-rw-r--r--media/java/android/media/RoutingSessionInfo.java190
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java108
-rw-r--r--media/java/android/media/flags/fade_manager_configuration.aconfig8
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig14
-rw-r--r--media/java/android/media/session/PlaybackState.java22
-rw-r--r--media/jni/android_media_tv_Tuner.cpp66
-rw-r--r--media/jni/android_media_tv_Tuner.h1
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java2
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java57
-rw-r--r--native/android/performance_hint.cpp74
-rw-r--r--packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml87
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransport.java3
-rw-r--r--packages/SettingsLib/AdaptiveIcon/Android.bp7
-rw-r--r--packages/SettingsLib/Android.bp3
-rw-r--r--packages/SettingsLib/EmergencyNumber/Android.bp3
-rw-r--r--packages/SettingsLib/MainSwitchPreference/Android.bp3
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/Android.bp3
-rw-r--r--packages/SettingsLib/SchedulesProvider/Android.bp5
-rw-r--r--packages/SettingsLib/SearchProvider/Android.bp3
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt20
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt71
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt1
-rw-r--r--packages/SettingsLib/Tile/Android.bp5
-rw-r--r--packages/SettingsLib/Utils/Android.bp3
-rw-r--r--packages/SettingsLib/search/Android.bp6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java21
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java3
-rw-r--r--packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java5
-rw-r--r--packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java5
-rw-r--r--packages/SettingsProvider/Android.bp2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java67
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig8
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java83
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/aconfig/Android.bp3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig35
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt36
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt51
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt274
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt)25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt71
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt125
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt111
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt81
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt150
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt200
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt54
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt156
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt35
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt101
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt59
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt230
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt21
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt8
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt37
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt29
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt19
-rw-r--r--packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt8
-rw-r--r--packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt371
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt80
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt4
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt24
-rw-r--r--packages/SystemUI/res/layout/widget_picker.xml30
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/ids.xml4
-rw-r--r--packages/SystemUI/shared/Android.bp12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt207
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java (renamed from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt125
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt)36
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt (renamed from packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt)13
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt220
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt53
-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.java93
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java)13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt157
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt139
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt749
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt618
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt161
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt47
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt)13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt)19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/unfold/Android.bp3
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt48
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java71
-rw-r--r--services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java12
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java54
-rw-r--r--services/autofill/java/com/android/server/autofill/ViewState.java5
-rw-r--r--services/backup/Android.bp8
-rw-r--r--services/companion/Android.bp3
-rw-r--r--services/companion/java/com/android/server/companion/PackageUtils.java7
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java4
-rw-r--r--services/companion/java/com/android/server/companion/transport/Transport.java76
-rw-r--r--services/core/Android.bp6
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/OWNERS1
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java38
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java27
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java9
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerUtils.java8
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java1
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java15
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java82
-rw-r--r--services/core/java/com/android/server/app/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java116
-rw-r--r--services/core/java/com/android/server/audio/FadeConfigurations.java427
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java220
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java28
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java106
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java64
-rw-r--r--services/core/java/com/android/server/audio/PlayerFocusEnforcer.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java58
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java20
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java36
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java41
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java49
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java78
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerControllerInterface.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java15
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java8
-rw-r--r--services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java107
-rw-r--r--services/core/java/com/android/server/flags/OWNERS3
-rw-r--r--services/core/java/com/android/server/flags/compaction.aconfig8
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java32
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java59
-rw-r--r--services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java13
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java19
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java28
-rw-r--r--services/core/java/com/android/server/location/provider/MockLocationProvider.java7
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java5
-rw-r--r--services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java17
-rw-r--r--services/core/java/com/android/server/media/DeviceRouteController.java24
-rw-r--r--services/core/java/com/android/server/media/LegacyDeviceRouteController.java40
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java21
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java26
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java261
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java57
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java10
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java32
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java11
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java68
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java184
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java13
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java75
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/InitAppsHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java32
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceInjector.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageSessionVerifier.java5
-rw-r--r--services/core/java/com/android/server/pm/ParallelPackageParser.java9
-rw-r--r--services/core/java/com/android/server/pm/Settings.java9
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageCacher.java5
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageParserUtils.java79
-rw-r--r--services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java16
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java79
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java28
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java86
-rw-r--r--services/core/java/com/android/server/wm/Transition.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java44
-rw-r--r--services/core/jni/OWNERS4
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd23
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt22
-rw-r--r--services/java/com/android/server/SystemServer.java6
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java8
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java2
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java250
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java65
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java54
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java288
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java70
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java25
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java135
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java506
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java25
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java235
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java18
-rw-r--r--services/usage/java/com/android/server/usage/StorageStatsService.java69
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java76
-rw-r--r--services/usb/Android.bp3
-rw-r--r--telecomm/java/android/telecom/AuthenticatorService.java101
-rw-r--r--telecomm/java/android/telecom/Call.java94
-rw-r--r--telecomm/java/android/telecom/Connection.java31
-rw-r--r--telephony/java/android/telephony/AnomalyReporter.java13
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java157
-rw-r--r--telephony/java/android/telephony/SecurityAlgorithmUpdate.java214
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java88
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java124
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java62
-rw-r--r--tests/ActivityManagerPerfTests/tests/Android.bp6
-rw-r--r--tools/hoststubgen/README.md5
-rw-r--r--tools/hoststubgen/hoststubgen/Android.bp225
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java40
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java47
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java30
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java46
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java44
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java45
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java30
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java47
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java46
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java36
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java24
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/Android.bp19
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp57
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml29
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/README.md22
-rwxr-xr-xtools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh85
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java472
-rw-r--r--tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java41
-rwxr-xr-xtools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh35
-rwxr-xr-xtools/hoststubgen/scripts/run-all-tests.sh5
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt4
639 files changed, 13847 insertions, 9182 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 705a4df6c30f..c231b30503fa 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -57,7 +57,7 @@ aconfig_srcjars = [
":android.app.flags-aconfig-java{.generated_srcjars}",
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}",
+ ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
@@ -430,10 +430,7 @@ java_aconfig_library {
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
package: "com.android.media.flags",
- srcs: [
- "media/java/android/media/flags/media_better_together.aconfig",
- "media/java/android/media/flags/fade_manager_configuration.aconfig",
- ],
+ srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
}
java_aconfig_library {
@@ -591,16 +588,16 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-// Pinner Service
+// Server Services Flags
aconfig_declarations {
- name: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig",
package: "com.android.server.flags",
- srcs: ["services/core/java/com/android/server/flags/pinner.aconfig"],
+ srcs: ["services/core/java/com/android/server/flags/*.aconfig"],
}
java_aconfig_library {
- name: "com.android.server.flags.pinner-aconfig-java",
- aconfig_declarations: "com.android.server.flags.pinner-aconfig",
+ name: "com.android.server.flags.services-aconfig-java",
+ aconfig_declarations: "com.android.server.flags.services-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
diff --git a/Android.bp b/Android.bp
index bb9304819e80..13b170353dd6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -174,6 +174,9 @@ java_library {
// and remove this line.
"//frameworks/base/tools/hoststubgen:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// AIDL files under these paths are mixture of public and private ones.
@@ -264,6 +267,9 @@ java_library {
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -432,6 +438,9 @@ java_library {
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -475,6 +484,9 @@ java_library {
],
compile_dex: false,
headers_only: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -502,6 +514,9 @@ java_library {
"-Xep:AndroidFrameworkUid:ERROR",
],
},
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -516,6 +531,7 @@ java_library {
},
lint: {
enabled: false,
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -540,6 +556,9 @@ java_library {
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -555,6 +574,9 @@ java_library {
"calendar-provider-compat-config",
"contacts-provider-platform-compat-config",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
platform_compat_config {
@@ -609,6 +631,9 @@ java_library {
"rappor",
],
dxflags: ["--core-library"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// utility classes statically linked into framework-wifi and dynamically linked
@@ -634,6 +659,9 @@ java_library {
"//frameworks/base/services/net",
"//packages/modules/Wifi/framework",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
filegroup {
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 03f3f0f3d61d..f330ad14ea57 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -65,7 +65,7 @@ java_genrule {
// depend on it.
java_genrule {
name: "framework-minus-apex.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
cmd: "cp $(in) $(out)",
srcs: [
":framework-minus-apex.ravenwood-base{ravenwood.jar}",
diff --git a/SECURITY_STATE_OWNERS b/SECURITY_STATE_OWNERS
new file mode 100644
index 000000000000..30ddfe2bebe8
--- /dev/null
+++ b/SECURITY_STATE_OWNERS
@@ -0,0 +1,5 @@
+alxu@google.com
+musashi@google.com
+maunik@google.com
+davidkwak@google.com
+willcoster@google.com \ No newline at end of file
diff --git a/apct-tests/perftests/healthconnect/Android.bp b/apct-tests/perftests/healthconnect/Android.bp
new file mode 100644
index 000000000000..c2d0a6f200a0
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+ // 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"],
+}
+
+android_test {
+ name: "HealthConnectPerfTests",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "apct-perftests-utils",
+ "collector-device-lib-platform",
+ ],
+
+ libs: ["android.test.base"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+ certificate: "platform",
+}
diff --git a/apct-tests/perftests/healthconnect/AndroidManifest.xml b/apct-tests/perftests/healthconnect/AndroidManifest.xml
new file mode 100644
index 000000000000..6a6370b6214a
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.healthconnect">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.healthconnect"/>
+</manifest>
diff --git a/apct-tests/perftests/healthconnect/AndroidTest.xml b/apct-tests/perftests/healthconnect/AndroidTest.xml
new file mode 100644
index 000000000000..5036202a9f4a
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidTest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs HealthConnectPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="HealthConnectPerfTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.healthconnect" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+ </test>
+</configuration>
diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
new file mode 100644
index 000000000000..da0b46adeaef
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1219472
+
+arkivanov@google.com
+jstembridge@google.com
+pratyushmore@google.com
+itsleo@google.com
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
new file mode 100644
index 000000000000..21a4ca048f62
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.perftests.healthconnect
+
+import android.health.connect.HealthConnectManager
+import android.os.SystemClock
+import android.perftests.utils.PerfStatusReporter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Read/write benchmark tests for [HealthConnectManager]
+ *
+ * Build/Install/Run: atest HealthConnectReadWritePerfTest
+ */
+@RunWith(AndroidJUnit4::class)
+class HealthConnectReadWritePerfTest {
+
+ @get:Rule
+ val perfStatusReporter = PerfStatusReporter()
+
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+
+ private val manager by lazy {
+ requireNotNull(context.getSystemService(HealthConnectManager::class.java))
+ }
+
+ /**
+ * A first empty test just to setup the test package and make sure it runs properly.
+ */
+ @Test
+ fun placeholder() {
+ val state = perfStatusReporter.benchmarkState
+ while (state.keepRunning()) {
+ SystemClock.sleep(100)
+ }
+ }
+} \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 903248f5f0b4..d940e380fa7e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -159,7 +159,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -302,8 +301,6 @@ public class JobSchedulerService extends com.android.server.SystemService
private final ConnectivityController mConnectivityController;
/** Need directly for sending uid state changes */
private final DeviceIdleJobsController mDeviceIdleJobsController;
- /** Need directly for sending exempted bucket changes */
- private final FlexibilityController mFlexibilityController;
/** Needed to get next estimated launch time. */
private final PrefetchController mPrefetchController;
/** Needed to get remaining quota time. */
@@ -516,10 +513,6 @@ public class JobSchedulerService extends com.android.server.SystemService
if (name == null) {
continue;
}
- if (DEBUG) {
- Slog.d(TAG, "DeviceConfig " + name
- + " changed to " + properties.getString(name, null));
- }
switch (name) {
case Constants.KEY_ENABLE_API_QUOTAS:
case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
@@ -1837,7 +1830,12 @@ public class JobSchedulerService extends com.android.server.SystemService
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
jobStatus.getJob().getIntervalMillis(),
- jobStatus.getJob().getFlexMillis());
+ jobStatus.getJob().getFlexMillis(),
+ jobStatus.hasFlexibilityConstraint(),
+ /* isFlexConstraintSatisfied */ false,
+ jobStatus.canApplyTransportAffinities(),
+ jobStatus.getNumAppliedFlexibleConstraints(),
+ jobStatus.getNumDroppedFlexibleConstraints());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2280,7 +2278,12 @@ public class JobSchedulerService extends com.android.server.SystemService
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
cancelled.getJob().getIntervalMillis(),
- cancelled.getJob().getFlexMillis());
+ cancelled.getJob().getFlexMillis(),
+ cancelled.hasFlexibilityConstraint(),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ cancelled.canApplyTransportAffinities(),
+ cancelled.getNumAppliedFlexibleConstraints(),
+ cancelled.getNumDroppedFlexibleConstraints());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -2539,17 +2542,17 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers = new ArrayList<StateController>();
mPrefetchController = new PrefetchController(this);
mControllers.add(mPrefetchController);
- mFlexibilityController =
+ final FlexibilityController flexibilityController =
new FlexibilityController(this, mPrefetchController);
- mControllers.add(mFlexibilityController);
+ mControllers.add(flexibilityController);
mConnectivityController =
- new ConnectivityController(this, mFlexibilityController);
+ new ConnectivityController(this, flexibilityController);
mControllers.add(mConnectivityController);
mControllers.add(new TimeController(this));
- final IdleController idleController = new IdleController(this, mFlexibilityController);
+ final IdleController idleController = new IdleController(this, flexibilityController);
mControllers.add(idleController);
final BatteryController batteryController =
- new BatteryController(this, mFlexibilityController);
+ new BatteryController(this, flexibilityController);
mControllers.add(batteryController);
mStorageController = new StorageController(this);
mControllers.add(mStorageController);
@@ -3198,13 +3201,6 @@ public class JobSchedulerService extends com.android.server.SystemService
}
@Override
- public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
- if (changedJobs.size() > 0) {
- mFlexibilityController.onExemptedBucketChanged(changedJobs);
- }
- }
-
- @Override
public void onRestrictionStateChanged(@NonNull JobRestriction restriction,
boolean stopOvertimeJobs) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
@@ -3511,10 +3507,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
final boolean shouldForceBatchJob;
- if (job.overrideState > JobStatus.OVERRIDE_NONE) {
- // The job should run for some test. Don't force batch it.
- shouldForceBatchJob = false;
- } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
+ if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
// Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
shouldForceBatchJob = false;
} else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
@@ -4967,8 +4960,6 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
+ " " + jobId + " s=" + satisfied + " f=" + force);
- final CountDownLatch delayLatch = new CountDownLatch(1);
- final JobStatus js;
try {
final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4977,7 +4968,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
synchronized (mLock) {
- js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (js == null) {
return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
}
@@ -4988,71 +4979,23 @@ public class JobSchedulerService extends com.android.server.SystemService
// Re-evaluate constraints after the override is set in case one of the overridden
// constraints was preventing another constraint from thinking it needed to update.
for (int c = mControllers.size() - 1; c >= 0; --c) {
- mControllers.get(c).evaluateStateLocked(js);
+ mControllers.get(c).reevaluateStateLocked(uid);
}
if (!js.isConstraintsSatisfied()) {
- if (js.hasConnectivityConstraint()
- && !js.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)
- && js.wouldBeReadyWithConstraint(JobStatus.CONSTRAINT_CONNECTIVITY)) {
- // Because of how asynchronous the connectivity signals are, JobScheduler
- // may not get the connectivity satisfaction signal immediately. In this
- // case, wait a few seconds to see if it comes in before saying the
- // connectivity constraint isn't satisfied.
- mHandler.postDelayed(
- checkConstraintRunnableForTesting(
- mHandler, js, delayLatch, 5, 1000),
- 1000);
- } else {
- // There's no asynchronous signal to wait for. We can immediately say the
- // job's constraints aren't satisfied and return.
- js.overrideState = JobStatus.OVERRIDE_NONE;
- return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
- }
- } else {
- delayLatch.countDown();
+ js.overrideState = JobStatus.OVERRIDE_NONE;
+ return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
}
+
+ queueReadyJobsForExecutionLocked();
+ maybeRunPendingJobsLocked();
}
} catch (RemoteException e) {
// can't happen
- return 0;
- }
-
- // Choose to block the return until we're sure about the state of the connectivity job
- // so that tests can expect a reliable state after calling the run command.
- try {
- delayLatch.await(7L, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Slog.e(TAG, "Couldn't wait for asynchronous constraint change", e);
- }
-
- synchronized (mLock) {
- if (!js.isConstraintsSatisfied()) {
- js.overrideState = JobStatus.OVERRIDE_NONE;
- return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
- }
-
- queueReadyJobsForExecutionLocked();
- maybeRunPendingJobsLocked();
}
return 0;
}
- private static Runnable checkConstraintRunnableForTesting(@NonNull final Handler handler,
- @NonNull final JobStatus js, @NonNull final CountDownLatch latch,
- final int remainingAttempts, final long delayMs) {
- return () -> {
- if (remainingAttempts <= 0 || js.isConstraintsSatisfied()) {
- latch.countDown();
- return;
- }
- handler.postDelayed(
- checkConstraintRunnableForTesting(
- handler, js, latch, remainingAttempts - 1, delayMs),
- delayMs);
- };
- }
-
// Shell command infrastructure: immediately timeout currently executing jobs
int executeStopCommand(PrintWriter pw, String pkgName, int userId,
@Nullable String namespace, boolean hasJobId, int jobId,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3addf9f98db2..fe55e2702916 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -536,7 +536,12 @@ public final class JobServiceContext implements ServiceConnection {
/* system_measured_calling_download_bytes */ 0,
/* system_measured_calling_upload_bytes */ 0,
job.getJob().getIntervalMillis(),
- job.getJob().getFlexMillis());
+ job.getJob().getFlexMillis(),
+ job.hasFlexibilityConstraint(),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ job.canApplyTransportAffinities(),
+ job.getNumAppliedFlexibleConstraints(),
+ job.getNumDroppedFlexibleConstraints());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1620,7 +1625,12 @@ public final class JobServiceContext implements ServiceConnection {
TrafficStats.getUidTxBytes(completedJob.getUid())
- mInitialUploadedBytesFromCalling,
completedJob.getJob().getIntervalMillis(),
- completedJob.getJob().getFlexMillis());
+ completedJob.getJob().getFlexMillis(),
+ completedJob.hasFlexibilityConstraint(),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ completedJob.canApplyTransportAffinities(),
+ completedJob.getNumAppliedFlexibleConstraints(),
+ completedJob.getNumDroppedFlexibleConstraints());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index 411a24de4bcb..50064bde0bbe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -62,12 +62,6 @@ public interface StateChangedListener {
/**
* Called when these jobs are added or removed from the
- * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_EXEMPTED} bucket.
- */
- void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> jobs);
-
- /**
- * Called when these jobs are added or removed from the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
*/
void onRestrictedBucketChanged(@NonNull List<JobStatus> jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e4cb569d3d35..0e67b9ac944f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -20,7 +20,6 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -181,12 +180,8 @@ public final class FlexibilityController extends StateController {
}
};
- private static final int MSG_CHECK_ALL_JOBS = 0;
- /** Check the jobs in {@link #mJobsToCheck} */
- private static final int MSG_CHECK_JOBS = 1;
-
- @GuardedBy("mLock")
- private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();
+ private static final int MSG_UPDATE_JOBS = 0;
+ private static final int MSG_UPDATE_JOB = 1;
public FlexibilityController(
JobSchedulerService service, PrefetchController prefetchController) {
@@ -271,14 +266,7 @@ public final class FlexibilityController extends StateController {
@GuardedBy("mLock")
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
return !mFlexibilityEnabled
- // Exclude all jobs of the TOP app
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
- // Only exclude DEFAULT+ priority jobs for BFGS+ apps
- || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
- && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
- // Only exclude DEFAULT+ priority jobs for EXEMPTED apps
- || (js.getStandbyBucket() == EXEMPTED_INDEX
- && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
|| hasEnoughSatisfiedConstraintsLocked(js)
|| mService.isCurrentlyRunningLocked(js);
}
@@ -383,19 +371,11 @@ public final class FlexibilityController extends StateController {
// Push the job update to the handler to avoid blocking other controllers and
// potentially batch back-to-back controller state updates together.
- mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
+ mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
}
}
}
- /** Called with a set of apps who have been added to or removed from the exempted bucket. */
- public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
- synchronized (mLock) {
- mJobsToCheck.addAll(changedJobs);
- mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
- }
- }
-
/** Checks if the given constraint is satisfied in the flexibility controller. */
@VisibleForTesting
boolean isConstraintSatisfied(int constraint) {
@@ -505,9 +485,7 @@ public final class FlexibilityController extends StateController {
@Override
@GuardedBy("mLock")
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
- if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
- && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
- // All changes are below BFGS. There's no significant change to care about.
+ if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
return;
}
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -732,8 +710,7 @@ public final class FlexibilityController extends StateController {
}
mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
js.getNumAppliedFlexibleConstraints());
- mJobsToCheck.add(js);
- mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+ mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
return;
}
if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -784,11 +761,10 @@ public final class FlexibilityController extends StateController {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CHECK_ALL_JOBS:
- removeMessages(MSG_CHECK_ALL_JOBS);
+ case MSG_UPDATE_JOBS:
+ removeMessages(MSG_UPDATE_JOBS);
synchronized (mLock) {
- mJobsToCheck.clear();
final long nowElapsed = sElapsedRealtimeClock.millis();
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
@@ -814,25 +790,19 @@ public final class FlexibilityController extends StateController {
}
break;
- case MSG_CHECK_JOBS:
+ case MSG_UPDATE_JOB:
synchronized (mLock) {
- final long nowElapsed = sElapsedRealtimeClock.millis();
- ArraySet<JobStatus> changedJobs = new ArraySet<>();
-
- for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
- final JobStatus js = mJobsToCheck.valueAt(i);
- if (DEBUG) {
- Slog.d(TAG, "Checking on " + js.toShortString());
- }
- if (js.setFlexibilityConstraintSatisfied(
- nowElapsed, isFlexibilitySatisfiedLocked(js))) {
- changedJobs.add(js);
- }
+ final JobStatus js = (JobStatus) msg.obj;
+ if (DEBUG) {
+ Slog.d("blah", "Checking on " + js.toShortString());
}
-
- mJobsToCheck.clear();
- if (changedJobs.size() > 0) {
- mStateChangedListener.onControllerStateChanged(changedJobs);
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ // TODO(141645789): add method that will take a single job
+ ArraySet<JobStatus> changedJob = new ArraySet<>();
+ changedJob.add(js);
+ mStateChangedListener.onControllerStateChanged(changedJob);
}
}
break;
@@ -1015,10 +985,7 @@ public final class FlexibilityController extends StateController {
pw.println(":");
pw.increaseIndent();
- pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
- pw.print("(");
- JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
- pw.println(")");
+ pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index a095a16817d8..bdc2246475d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -122,7 +122,7 @@ public final class JobStatus {
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
- static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
private static final int IMPLICIT_CONSTRAINTS = 0
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
@@ -1531,7 +1531,7 @@ public final class JobStatus {
/**
* Returns the number of required flexible job constraints that have been dropped with time.
- * The lower this number is the easier it is for the flexibility constraint to be satisfied.
+ * The higher this number is the easier it is for the flexibility constraint to be satisfied.
*/
public int getNumDroppedFlexibleConstraints() {
return mNumDroppedFlexibleConstraints;
@@ -1597,7 +1597,8 @@ public final class JobStatus {
mTransportAffinitiesSatisfied = isSatisfied;
}
- boolean canApplyTransportAffinities() {
+ /** Whether transport affinities can be applied to the job in flex scheduling. */
+ public boolean canApplyTransportAffinities() {
return mCanApplyTransportAffinities;
}
@@ -2191,7 +2192,7 @@ public final class JobStatus {
* @return Whether or not this job would be ready to run if it had the specified constraint
* granted, based on its requirements.
*/
- public boolean wouldBeReadyWithConstraint(int constraint) {
+ boolean wouldBeReadyWithConstraint(int constraint) {
return readinessStatusWithConstraint(constraint, true);
}
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 04da7814244e..8ddbf691359f 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
@@ -2511,7 +2511,6 @@ public final class QuotaController extends StateController {
+ " to bucketIndex " + bucketIndex);
}
List<JobStatus> restrictedChanges = new ArrayList<>();
- ArraySet<JobStatus> exemptedChanges = new ArraySet<>();
synchronized (mLock) {
ShrinkableDebits debits = mEJStats.get(userId, packageName);
if (debits != null) {
@@ -2531,10 +2530,6 @@ public final class QuotaController extends StateController {
&& bucketIndex != js.getStandbyBucket()) {
restrictedChanges.add(js);
}
- if ((bucketIndex == EXEMPTED_INDEX || js.getStandbyBucket() == EXEMPTED_INDEX)
- && bucketIndex != js.getStandbyBucket()) {
- exemptedChanges.add(js);
- }
js.setStandbyBucket(bucketIndex);
}
Timer timer = mPkgTimers.get(userId, packageName);
@@ -2549,9 +2544,6 @@ public final class QuotaController extends StateController {
maybeUpdateConstraintForPkgLocked(
sElapsedRealtimeClock.millis(), userId, packageName));
}
- if (exemptedChanges.size() > 0) {
- mStateChangedListener.onExemptedBucketChanged(exemptedChanges);
- }
if (restrictedChanges.size() > 0) {
mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 913a76a65026..4d4e3407a3c3 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -591,6 +591,16 @@ public class AppIdleHistory {
if (idle) {
newBucket = IDLE_BUCKET_CUTOFF;
reason = REASON_MAIN_FORCED_BY_USER;
+ final AppUsageHistory appHistory = getAppUsageHistory(packageName, userId,
+ elapsedRealtime);
+ // Wipe all expiry times that could raise the bucket on reevaluation.
+ if (appHistory.bucketExpiryTimesMs != null) {
+ for (int i = appHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) {
+ if (appHistory.bucketExpiryTimesMs.keyAt(i) < newBucket) {
+ appHistory.bucketExpiryTimesMs.removeAt(i);
+ }
+ }
+ }
} else {
newBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
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 bd5c00600044..e589c214801a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -59,6 +59,7 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.usage.AppIdleHistory.STANDBY_BUCKET_UNKNOWN;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -2146,6 +2147,15 @@ public class AppStandbyController
}
}
+ /**
+ * Flush the handler.
+ * Returns true if successfully flushed within the timeout, otherwise return false.
+ */
+ @VisibleForTesting
+ boolean flushHandler(@DurationMillisLong long timeoutMillis) {
+ return mHandler.runWithScissors(() -> {}, timeoutMillis);
+ }
+
@Override
public void flushToDisk() {
synchronized (mAppIdleLock) {
diff --git a/api/Android.bp b/api/Android.bp
index 9029d252b470..363197a54fac 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -307,10 +307,6 @@ stubs_defaults {
"framework-protos",
],
flags: [
- "--api-lint-ignore-prefix android.icu.",
- "--api-lint-ignore-prefix java.",
- "--api-lint-ignore-prefix junit.",
- "--api-lint-ignore-prefix org.",
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
"--error UnflaggedApi",
diff --git a/core/api/current.txt b/core/api/current.txt
index d3ed89b347ec..ec8f0706379c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9315,10 +9315,14 @@ package android.app.usage {
public final class StorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getAppBytes();
+ method @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public long getAppBytesByDataType(int);
method public long getCacheBytes();
method public long getDataBytes();
method public long getExternalCacheBytes();
method public void writeToParcel(android.os.Parcel, int);
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
+ field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
@@ -13418,12 +13422,12 @@ package android.content.pm {
public final class SigningInfo implements android.os.Parcelable {
ctor public SigningInfo();
- ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(@IntRange(from=0) int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
+ ctor @FlaggedApi("android.content.pm.archiving") public SigningInfo(int, @Nullable java.util.Collection<android.content.pm.Signature>, @Nullable java.util.Collection<java.security.PublicKey>, @Nullable java.util.Collection<android.content.pm.Signature>);
ctor public SigningInfo(android.content.pm.SigningInfo);
method public int describeContents();
method public android.content.pm.Signature[] getApkContentsSigners();
method @FlaggedApi("android.content.pm.archiving") @NonNull public java.util.Collection<java.security.PublicKey> getPublicKeys();
- method @FlaggedApi("android.content.pm.archiving") @IntRange(from=0) public int getSchemeVersion();
+ method @FlaggedApi("android.content.pm.archiving") public int getSchemeVersion();
method public android.content.pm.Signature[] getSigningCertificateHistory();
method public boolean hasMultipleSigners();
method public boolean hasPastSigningCertificates();
@@ -23972,6 +23976,7 @@ package android.media {
method @Nullable public android.net.Uri getIconUri();
method @NonNull public String getId();
method @NonNull public CharSequence getName();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
method public int getType();
method public int getVolume();
method public int getVolumeHandling();
@@ -23989,6 +23994,9 @@ package android.media {
field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1
field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
@@ -24029,6 +24037,7 @@ package android.media {
method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -24242,6 +24251,7 @@ package android.media {
method public void release();
method public void selectRoute(@NonNull android.media.MediaRoute2Info);
method public void setVolume(int);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
}
public abstract static class MediaRouter2.TransferCallback {
@@ -24639,12 +24649,16 @@ package android.media {
method @Nullable public CharSequence getName();
method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason();
method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0
+ field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1
}
public static final class RoutingSessionInfo.Builder {
@@ -24665,6 +24679,8 @@ package android.media {
method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String);
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int);
@@ -43277,6 +43293,7 @@ package android.telephony {
field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array";
field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
@@ -43403,6 +43420,7 @@ package android.telephony {
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.hide_prefer_3g_item") public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_monthly_notification_count_int";
field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
@@ -45271,6 +45289,7 @@ package android.telephony {
method @Nullable public String getMncString();
method @Deprecated public String getNumber();
method public int getPortIndex();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public int getSubscriptionType();
@@ -45352,6 +45371,9 @@ package android.telephony {
field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1
field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
@@ -45600,6 +45622,8 @@ package android.telephony {
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable();
+ method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable();
method public boolean isEmergencyNumber(@NonNull String);
method public boolean isHearingAidCompatibilitySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
@@ -45609,9 +45633,9 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int);
method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
method public boolean isRttSupported();
- method public boolean isSmsCapable();
+ method @Deprecated public boolean isSmsCapable();
method @Deprecated public boolean isTtyModeSupported();
- method public boolean isVoiceCapable();
+ method @Deprecated public boolean isVoiceCapable();
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
@@ -45658,6 +45682,7 @@ package android.telephony {
field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED";
field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+ field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
@@ -53524,6 +53549,7 @@ package android.view {
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
method @Nullable public android.view.WindowInsetsController getInsetsController();
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
@@ -53601,6 +53627,7 @@ package android.view {
method public abstract void setFeatureInt(int, int);
method public void setFlags(int, int);
method public void setFormat(int);
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
method public void setGravity(int);
method @RequiresPermission(android.Manifest.permission.HIDE_OVERLAY_WINDOWS) public final void setHideOverlayWindows(boolean);
method public void setIcon(@DrawableRes int);
@@ -53945,6 +53972,7 @@ package android.view {
method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
method public int getFitInsetsSides();
method public int getFitInsetsTypes();
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
method public final CharSequence getTitle();
method public boolean isFitInsetsIgnoringVisibility();
method public boolean isHdrConversionEnabled();
@@ -53956,6 +53984,7 @@ package android.view {
method public void setFitInsetsIgnoringVisibility(boolean);
method public void setFitInsetsSides(int);
method public void setFitInsetsTypes(int);
+ method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
method public void setHdrConversionEnabled(boolean);
method public final void setTitle(CharSequence);
method public void setWallpaperTouchEventsEnabled(boolean);
@@ -57383,7 +57412,7 @@ package android.webkit {
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57429,7 +57458,7 @@ package android.webkit {
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 63b142926653..b15bc0e589b1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3357,7 +3357,7 @@ package android.companion.virtual.camera {
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
method public int describeContents();
- method @StringRes public int getDisplayNameStringRes();
+ method @NonNull public String getName();
method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
@@ -3367,7 +3367,7 @@ package android.companion.virtual.camera {
ctor public VirtualCameraConfig.Builder();
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@@ -6472,6 +6472,7 @@ package android.media {
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchAudioFocusChangeWithFade(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy, @NonNull java.util.List<android.media.AudioFocusInfo>, @Nullable android.media.FadeManagerConfiguration);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getActiveAssistantServicesUids();
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getAssistantServicesUids();
@@ -6671,6 +6672,69 @@ package android.media {
field public static final int CONTENT_ID_NONE = 0; // 0x0
}
+ @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+ method public long getFadeInDelayForOffenders();
+ method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeInDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int);
+ method public long getFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method public long getFadeOutDurationForUsage(int);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable public android.media.VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int);
+ method public int getFadeState();
+ method @NonNull public java.util.List<java.lang.Integer> getFadeableUsages();
+ method @NonNull public java.util.List<android.media.AudioAttributes> getUnfadeableAudioAttributes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableContentTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeablePlayerTypes();
+ method @NonNull public java.util.List<java.lang.Integer> getUnfadeableUids();
+ method public boolean isAudioAttributesUnfadeable(@NonNull android.media.AudioAttributes);
+ method public boolean isContentTypeUnfadeable(int);
+ method public boolean isFadeEnabled();
+ method public boolean isPlayerTypeUnfadeable(int);
+ method public boolean isUidUnfadeable(int);
+ method public boolean isUsageFadeable(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
+ field public static final long DURATION_NOT_SET = 0L; // 0x0L
+ field public static final int FADE_STATE_DISABLED = 0; // 0x0
+ field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
+ field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
+ field public static final String TAG = "FadeManagerConfiguration";
+ field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
+ }
+
+ public static final class FadeManagerConfiguration.Builder {
+ ctor public FadeManagerConfiguration.Builder();
+ ctor public FadeManagerConfiguration.Builder(long, long);
+ ctor public FadeManagerConfiguration.Builder(@NonNull android.media.FadeManagerConfiguration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration build();
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutDurationForUsage(int, long);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForAudioAttributes(@NonNull android.media.AudioAttributes, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeOutVolumeShaperConfigForUsage(int, @Nullable android.media.VolumeShaper.Configuration);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeState(int);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeableUsages(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableAudioAttributes(@NonNull java.util.List<android.media.AudioAttributes>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableContentTypes(@NonNull java.util.List<java.lang.Integer>);
+ method @NonNull public android.media.FadeManagerConfiguration.Builder setUnfadeableUids(@NonNull java.util.List<java.lang.Integer>);
+ }
+
public class HwAudioSource {
method public boolean isPlaying();
method public void start();
@@ -6889,15 +6953,18 @@ package android.media.audiopolicy {
public class AudioPolicy {
method public int attachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss();
method public android.media.AudioRecord createAudioRecordSink(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public android.media.FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
method public int getFocusDuckingBehavior();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
+ method @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss(@NonNull android.media.FadeManagerConfiguration);
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index aaeba66e4a40..812ba6d5fde9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -510,6 +510,7 @@ package android.app {
method public int getActivityType();
method @Nullable public android.graphics.Rect getAppBounds();
method @NonNull public android.graphics.Rect getBounds();
+ method public int getDisplayRotation();
method @NonNull public android.graphics.Rect getMaxBounds();
method public int getRotation();
method public int getWindowingMode();
@@ -3611,6 +3612,10 @@ package android.view {
method @Nullable public android.view.View getStatusBarBackgroundView();
}
+ public static final class WindowInsets.Type {
+ method @NonNull public static String toString(int);
+ }
+
public interface WindowManager extends android.view.ViewManager {
method public default int getDisplayImePolicy(int);
method public static boolean hasWindowExtensionsEnabled();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6ddb36a72aa9..6df0f6b2ac8b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -407,7 +407,7 @@ public final class ActivityThread extends ClientTransactionHandler
private int mLastSessionId;
// Holds the value of the last reported device ID value from the server for the top activity.
- int mLastReportedDeviceId;
+ int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
@UnsupportedAppUsage
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -4856,10 +4856,13 @@ public final class ActivityThread extends ClientTransactionHandler
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
if (!service.isUiContext()) { // WindowProviderService is a UI Context.
- VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
- if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
- || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
service.updateDeviceId(mLastReportedDeviceId);
+ } else {
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
}
}
service.onCreate();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b7db5f5d8b0c..c3adbc30641f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -222,7 +222,7 @@ interface INotificationManager
boolean removeAutomaticZenRule(String id, boolean fromUser);
boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
+ void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a510c7704751..476232cb40b3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7733,11 +7733,12 @@ public class Notification implements Parcelable
} else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) {
// If the icon contains a bitmap, use the old extra so that listeners which look
// for that extra can still find the picture. Don't include the new extra in
- // that case, to avoid duplicating data.
+ // that case, to avoid duplicating data. Leave the unused extra set to null to avoid
+ // crashing apps that came to expect it to be present but null.
extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap());
- extras.remove(EXTRA_PICTURE_ICON);
+ extras.putParcelable(EXTRA_PICTURE_ICON, null);
} else {
- extras.remove(EXTRA_PICTURE);
+ extras.putParcelable(EXTRA_PICTURE, null);
extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon);
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f76a45b37661..0b6e24cf5545 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1391,20 +1391,9 @@ public class NotificationManager {
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
- if (Flags.modesApi()) {
- setAutomaticZenRuleState(id, condition,
- /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
- } else {
- setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
- }
- }
-
- /** @hide */
- public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
- boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition, fromUser);
+ service.setAutomaticZenRuleState(id, condition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 772b0b46d3b4..47d19eda0c72 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -88,6 +88,10 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve
# Pinner
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
+# BackgroundInstallControlManager
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+
# ResourcesManager
per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 46216343dcff..aa3b71a28eba 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -27,6 +27,7 @@ import static android.view.Surface.rotationToString;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
@@ -326,7 +327,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * Sets the apparent display cutout.
+ * Sets the display rotation.
* @hide
*/
public void setDisplayRotation(@Surface.Rotation int rotation) {
@@ -386,9 +387,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * @see #setDisplayRotation
- * @hide
+ * Gets the display rotation.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public @Surface.Rotation int getDisplayRotation() {
return mDisplayRotation;
}
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 8d25d7bfb11b..87d97d55318a 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -17,11 +17,16 @@
package android.app.usage;
import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Storage statistics for a UID, package, or {@link UserHandle} on a single
* storage volume.
@@ -29,10 +34,47 @@ import android.os.UserHandle;
* @see StorageStatsManager
*/
public final class StorageStats implements Parcelable {
- /** {@hide} */ public long codeBytes;
- /** {@hide} */ public long dataBytes;
- /** {@hide} */ public long cacheBytes;
- /** {@hide} */ public long externalCacheBytes;
+ /** @hide */ public long codeBytes;
+ /** @hide */ public long dataBytes;
+ /** @hide */ public long cacheBytes;
+ /** @hide */ public long apkBytes;
+ /** @hide */ public long libBytes;
+ /** @hide */ public long dmBytes;
+ /** @hide */ public long externalCacheBytes;
+
+ /** Represents all .apk files in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+
+ /** Represents all .dm files in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the sum of sizes for files of this type.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+
+ /** Represents lib/ in application code path.
+ * Can be used as an input to {@link #getAppBytesByDataType(int)}
+ * to get the size of lib/ directory.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public static final int APP_DATA_TYPE_LIB = 2;
+
+ /**
+ * Keep in sync with the file types defined above.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ @IntDef(flag = false, value = {
+ APP_DATA_TYPE_FILE_TYPE_APK,
+ APP_DATA_TYPE_FILE_TYPE_DM,
+ APP_DATA_TYPE_LIB,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AppDataType {}
/**
* Return the size of app. This includes {@code APK} files, optimized
@@ -48,6 +90,27 @@ public final class StorageStats implements Parcelable {
}
/**
+ * Return the size of the specified data type. This includes files stored under
+ * application code path.
+ * <p>
+ * If there is more than one package inside a uid, the return represents the aggregated
+ * stats when query StorageStat for package or uid.
+ * The data is not collected and the return defaults to 0 when query StorageStats for user.
+ *
+ * <p>
+ * Data is isolated for each user on a multiuser device.
+ */
+ @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+ public long getAppBytesByDataType(@AppDataType int dataType) {
+ switch (dataType) {
+ case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
+ case APP_DATA_TYPE_LIB: return libBytes;
+ case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
+ default: return 0;
+ }
+ }
+
+ /**
* Return the size of all data. This includes files stored under
* {@link Context#getDataDir()}, {@link Context#getCacheDir()},
* {@link Context#getCodeCacheDir()}.
@@ -98,6 +161,9 @@ public final class StorageStats implements Parcelable {
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheBytes = in.readLong();
+ this.apkBytes = in.readLong();
+ this.libBytes = in.readLong();
+ this.dmBytes = in.readLong();
this.externalCacheBytes = in.readLong();
}
@@ -111,6 +177,9 @@ public final class StorageStats implements Parcelable {
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheBytes);
+ dest.writeLong(apkBytes);
+ dest.writeLong(libBytes);
+ dest.writeLong(dmBytes);
dest.writeLong(externalCacheBytes);
}
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index a611255c3817..4d9d911ed563 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -35,3 +35,10 @@ flag {
description: " Feature flag to support filter based event query API"
bug: "194321117"
}
+
+flag {
+ name: "get_app_bytes_by_data_type_api"
+ namespace: "system_performance"
+ description: "Feature flag for collecting app data size by file type API"
+ bug: "294088945"
+}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index c95b864c08bb..ec2e5fe23ab9 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -12,4 +12,11 @@ flag {
namespace: "app_widgets"
description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
bug: "245950570"
-} \ No newline at end of file
+}
+
+flag {
+ name: "remove_app_widget_service_io_from_critical_path"
+ namespace: "app_widgets"
+ description: "Move state file IO to non-critical path"
+ bug: "312949280"
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b11840e8a931..879656a89233 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -220,6 +220,12 @@ public final class CompanionDeviceManager {
*/
public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
/**
+ * Test message type without a response.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN
+ /**
* Message header assigned to the remote authentication handshakes.
*
* @hide
@@ -237,6 +243,18 @@ public final class CompanionDeviceManager {
* @hide
*/
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+ /**
+ * Message header assigned to the one-way message sent from the wearable device.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW
+ /**
+ * Message header assigned to the one-way message sent to the wearable device.
+ *
+ * @hide
+ */
+ public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
/**
* The length limit of Association tag.
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index a9392518d0c6..59fe9a1c127b 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -20,11 +20,9 @@ import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
-import android.content.res.Resources;
import android.graphics.ImageFormat;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,16 +43,16 @@ import java.util.concurrent.Executor;
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraConfig implements Parcelable {
- private final @StringRes int mNameStringRes;
+ private final String mName;
private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
private final IVirtualCameraCallback mCallback;
private VirtualCameraConfig(
- int displayNameStringRes,
+ @NonNull String name,
@NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
@NonNull Executor executor,
@NonNull VirtualCameraCallback callback) {
- mNameStringRes = displayNameStringRes;
+ mName = requireNonNull(name, "Missing name");
mStreamConfigurations =
Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
if (mStreamConfigurations.isEmpty()) {
@@ -68,7 +66,7 @@ public final class VirtualCameraConfig implements Parcelable {
}
private VirtualCameraConfig(@NonNull Parcel in) {
- mNameStringRes = in.readInt();
+ mName = in.readString8();
mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
mStreamConfigurations =
Set.of(
@@ -84,18 +82,18 @@ public final class VirtualCameraConfig implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mNameStringRes);
+ dest.writeString8(mName);
dest.writeStrongInterface(mCallback);
dest.writeParcelableArray(
mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
}
/**
- * @return The display name of this VirtualCamera
+ * @return The name of this VirtualCamera
*/
- @StringRes
- public int getDisplayNameStringRes() {
- return mNameStringRes;
+ @NonNull
+ public String getName() {
+ return mName;
}
/**
@@ -126,30 +124,22 @@ public final class VirtualCameraConfig implements Parcelable {
* <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
- * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
+ * <li>A camera name must be set with {@link #setName(String)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+ private String mName;
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
/**
- * Set the visible name of this camera for the user.
- *
- * <p>Sets the resource to a string representing a user readable name for this virtual
- * camera.
- *
- * @throws IllegalArgumentException if an invalid resource id is passed.
+ * Set the name of the virtual camera instance.
*/
@NonNull
- public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
- if (displayNameStringRes <= 0) {
- throw new IllegalArgumentException("Invalid resource passed for display name");
- }
- mDisplayNameStringRes = displayNameStringRes;
+ public Builder setName(@NonNull String name) {
+ mName = requireNonNull(name, "Display name cannot be null");
return this;
}
@@ -203,7 +193,7 @@ public final class VirtualCameraConfig implements Parcelable {
@NonNull
public VirtualCameraConfig build() {
return new VirtualCameraConfig(
- mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
+ mName, mStreamConfigurations, mCallbackExecutor, mCallback);
}
}
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 81cfc078c035..b919c4b065fd 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -55,6 +55,18 @@ public class PackageStats implements Parcelable {
/** Size of cache used by the application. (e.g., /data/data/<app>/cache) */
public long cacheSize;
+ /** Size of .apk files of the application. */
+ /** @hide */
+ public long apkSize;
+
+ /** Size of the libraries of the application. */
+ /** @hide */
+ public long libSize;
+
+ /** Size of the .dm files of the application. */
+ /** @hide */
+ public long dmSize;
+
/**
* Size of the secure container on external storage holding the
* application's code.
@@ -108,6 +120,18 @@ public class PackageStats implements Parcelable {
sb.append(" cache=");
sb.append(cacheSize);
}
+ if (apkSize != 0) {
+ sb.append(" apk=");
+ sb.append(apkSize);
+ }
+ if (libSize != 0) {
+ sb.append(" lib=");
+ sb.append(libSize);
+ }
+ if (dmSize != 0) {
+ sb.append(" dm=");
+ sb.append(dmSize);
+ }
if (externalCodeSize != 0) {
sb.append(" extCode=");
sb.append(externalCodeSize);
@@ -149,6 +173,9 @@ public class PackageStats implements Parcelable {
codeSize = source.readLong();
dataSize = source.readLong();
cacheSize = source.readLong();
+ apkSize = source.readLong();
+ libSize = source.readLong();
+ dmSize = source.readLong();
externalCodeSize = source.readLong();
externalDataSize = source.readLong();
externalCacheSize = source.readLong();
@@ -162,6 +189,9 @@ public class PackageStats implements Parcelable {
codeSize = pStats.codeSize;
dataSize = pStats.dataSize;
cacheSize = pStats.cacheSize;
+ apkSize = pStats.apkSize;
+ libSize = pStats.libSize;
+ dmSize = pStats.dmSize;
externalCodeSize = pStats.externalCodeSize;
externalDataSize = pStats.externalDataSize;
externalCacheSize = pStats.externalCacheSize;
@@ -179,6 +209,9 @@ public class PackageStats implements Parcelable {
dest.writeLong(codeSize);
dest.writeLong(dataSize);
dest.writeLong(cacheSize);
+ dest.writeLong(apkSize);
+ dest.writeLong(libSize);
+ dest.writeLong(dmSize);
dest.writeLong(externalCodeSize);
dest.writeLong(externalDataSize);
dest.writeLong(externalCacheSize);
@@ -198,6 +231,9 @@ public class PackageStats implements Parcelable {
&& codeSize == otherStats.codeSize
&& dataSize == otherStats.dataSize
&& cacheSize == otherStats.cacheSize
+ && apkSize == otherStats.apkSize
+ && libSize == otherStats.libSize
+ && dmSize == otherStats.dmSize
&& externalCodeSize == otherStats.externalCodeSize
&& externalDataSize == otherStats.externalDataSize
&& externalCacheSize == otherStats.externalCacheSize
@@ -208,7 +244,8 @@ public class PackageStats implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(packageName, userHandle, codeSize, dataSize,
- cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize,
+ apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+ externalDataSize, externalCacheSize, externalMediaSize,
externalObbSize);
}
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index 8c2197470a8b..bb09ad23f6ec 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -31,6 +31,8 @@ import com.android.internal.util.DataClass;
import libcore.util.HexEncoding;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
@@ -49,6 +51,7 @@ public final class SigningDetails implements Parcelable {
private static final String TAG = "SigningDetails";
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({SignatureSchemeVersion.UNKNOWN,
SignatureSchemeVersion.JAR,
SignatureSchemeVersion.SIGNING_BLOCK_V2,
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index a40770484ca5..23daaf2d4138 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -17,9 +17,9 @@
package android.content.pm;
import android.annotation.FlaggedApi;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -53,7 +53,7 @@ public final class SigningInfo implements Parcelable {
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public SigningInfo(@IntRange(from = 0) int schemeVersion,
+ public SigningInfo(@SignatureSchemeVersion int schemeVersion,
@Nullable Collection<Signature> apkContentsSigners,
@Nullable Collection<PublicKey> publicKeys,
@Nullable Collection<Signature> signingCertificateHistory) {
@@ -168,7 +168,7 @@ public final class SigningInfo implements Parcelable {
* schemas</a>
*/
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public @IntRange(from = 0) int getSchemeVersion() {
+ public @SignatureSchemeVersion int getSchemeVersion() {
return mSigningDetails.getSignatureSchemeVersion();
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 60d5c140e2b3..94bec3562bb0 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -123,3 +123,11 @@ flag {
bug: "306329516"
is_fixed_read_only: true
}
+
+flag {
+ name: "improve_home_app_behavior"
+ namespace: "package_manager_service"
+ description: "Feature flag to improve the uninstallation and preferred activity of home app."
+ bug: "310801107"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03dbb8b34..60ad8e81fcf4 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@ public class SystemSensorManager extends SensorManager {
private static native long nativeCreate(String opPackageName);
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
+ private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+ Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@ public class SystemSensorManager extends SensorManager {
// initialize the sensor list
for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+ if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+ } else {
+ if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ }
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
-
}
/** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889ddfa5d..665d8d280bc6 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+ * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+ * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
if (mAvailableSessionCharacteristicsKeys == null) {
- mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+ mAvailableSessionCharacteristicsKeys =
+ Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}
return mAvailableSessionCharacteristicsKeys;
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212a7525..64a62a9299b8 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@ public abstract class DisplayManagerInternal {
*/
boolean blockScreenOn(Runnable unblocker);
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(int mode);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f970426..41dee3ab035c 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
package android.nfc.cardemulation;
import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@ public final class ApduServiceInfo implements Parcelable {
// Set uid
mUid = si.applicationInfo.uid;
- mCategoryOtherServiceEnabled = false; // support other category
+ mCategoryOtherServiceEnabled = true; // support other category
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e042761dbe..0d073cc6c819 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,10 @@ flag {
description: "Enable NFC Polling Loop Notifications ST shim"
bug: "294217286"
}
+
+flag {
+ name: "enable_tag_detection_broadcasts"
+ namespace: "nfc"
+ description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+ bug: "306203494"
+}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 209a59583944..d3f2c7ae6e42 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -92,3 +92,6 @@ per-file PerformanceHintManager.java = file:/ADPF_OWNERS
per-file IThermal* = file:/THERMAL_OWNERS
per-file CoolingDevice.java = file:/THERMAL_OWNERS
per-file Temperature.java = file:/THERMAL_OWNERS
+
+# SecurityStateManager
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS \ No newline at end of file
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523ee23dd..80ec458cd715 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -757,7 +757,6 @@ public class Process {
@Nullable String invokeWith,
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
- boolean bindMountSyspropOverrides,
@Nullable String[] zygoteArgs) {
// Webview zygote can't access app private data files, so doesn't need to know its data
// info.
@@ -767,7 +766,7 @@ public class Process {
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
disabledCompatChanges, /* pkgDataInfoMap */ null,
/* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
- /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
+ /* bindMountAppStorageDirs */ false, /* bindMountSyspropOverrides */ false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40bb6ee9..df503e86a700 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* An exception specific to a service.
@@ -33,6 +34,7 @@ import android.annotation.SystemApi;
* @hide
*/
@SystemApi
+@RavenwoodKeepWholeClass
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4af657dab0cd..942ce971226b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14339,6 +14339,14 @@ public final class Settings {
public static final String MODE_RINGER = "mode_ringer";
/**
+ * Whether or not Alarm stream should always be muted with Ringer.
+ *
+ * @hide
+ */
+ public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
+ "mute_alarm_stream_with_ringer_mode";
+
+ /**
* Overlay display devices setting.
* The associated value is a specially formatted string that describes the
* size and density of simulated secondary display devices.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 72c436ee6d41..e4af2da57f40 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4940,6 +4940,13 @@ public final class Telephony {
*/
public static final String COLUMN_IS_NTN = "is_ntn";
+ /**
+ * TelephonyProvider column name to indicate the service capability bitmasks.
+ *
+ * @hide
+ */
+ public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5011,7 +5018,8 @@ public final class Telephony {
COLUMN_USER_HANDLE,
COLUMN_SATELLITE_ENABLED,
COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
- COLUMN_IS_NTN
+ COLUMN_IS_NTN,
+ COLUMN_SERVICE_CAPABILITIES
);
/**
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3c93a0..30524a1132fa 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@ flag {
description: "Collect sepolicy hash from sysfs"
bug: "308471499"
}
+
+flag {
+ name: "frp_enforcement"
+ namespace: "android_hw_security"
+ description: "This flag controls whether PDB enforces FRP"
+ bug: "290312729"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index cabab6c80bf5..0927d4519b9c 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsSourceProto.FRAME;
-import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.TYPE_NUMBER;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -353,13 +352,12 @@ public class InsetsSource implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, WindowInsets.Type.toString(mType));
- proto.write(TYPE_NUMBER, mType);
mFrame.dumpDebug(proto, FRAME);
if (mVisibleFrame != null) {
mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
}
proto.write(VISIBLE, mVisible);
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 34b288460a67..0ce61bb17774 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -21,11 +21,11 @@ import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
-import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE;
import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE;
import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
+import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -393,7 +393,6 @@ public class InsetsSourceConsumer {
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType));
proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
proto.write(IS_REQUESTED_VISIBLE, isShowRequested());
if (mSourceControl != null) {
@@ -406,6 +405,7 @@ public class InsetsSourceConsumer {
mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
}
proto.write(ANIMATION_STATE, mAnimationState);
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7ea93f5a7542..527c7ed8e5f6 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -20,7 +20,7 @@ import static android.graphics.PointProto.X;
import static android.graphics.PointProto.Y;
import static android.view.InsetsSourceControlProto.LEASH;
import static android.view.InsetsSourceControlProto.POSITION;
-import static android.view.InsetsSourceControlProto.TYPE;
+import static android.view.InsetsSourceControlProto.TYPE_NUMBER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -244,8 +244,6 @@ public class InsetsSourceControl implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, WindowInsets.Type.toString(mType));
-
final long surfaceToken = proto.start(POSITION);
proto.write(X, mSurfacePosition.x);
proto.write(Y, mSurfacePosition.y);
@@ -254,6 +252,8 @@ public class InsetsSourceControl implements Parcelable {
if (mLeash != null) {
mLeash.dumpDebug(proto, LEASH);
}
+
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cbbe7856178d..b957b31a5bb7 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@ import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -38,7 +37,6 @@ import android.annotation.RequiresPermission;
import android.annotation.Size;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
@@ -53,13 +51,8 @@ import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
import android.hardware.SyncFence;
import android.hardware.display.DeviceProductInfo;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplay;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.opengl.EGLDisplay;
import android.opengl.EGLSync;
@@ -68,8 +61,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -2355,92 +2346,6 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Because this API is now going through {@link DisplayManager}, orientation and displayRect
- * will automatically be computed based on configuration changes. Because of this, the params
- * orientation and displayRect are ignored
- *
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.",
- trackingBug = 247078497)
- public static void setDisplayProjection(IBinder displayToken, int orientation,
- Rect layerStackRect, Rect displayRect) {
- DisplayManagerGlobal.getInstance().resizeVirtualDisplay(
- IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(),
- layerStackRect.height(), 1);
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag "
- + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.",
- trackingBug = 247078497)
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
- if (b == null) {
- throw new UnsupportedOperationException();
- }
-
- IDisplayManager dm = IDisplayManager.Stub.asInterface(b);
- try {
- dm.setDisplayIdToMirror(displayToken, layerStack);
- } catch (RemoteException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.",
- trackingBug = 247078497)
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- IVirtualDisplayCallback virtualDisplayCallback =
- IVirtualDisplayCallback.Stub.asInterface(displayToken);
- DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
- dm.setVirtualDisplaySurface(virtualDisplayCallback, surface);
- }
-
- /**
- * Secure is no longer supported because this is only called from outside system which cannot
- * create secure displays.
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or "
- + "{@code DisplayManager#createVirtualDisplay()} instead.",
- trackingBug = 247078497)
- public static IBinder createDisplay(String name, boolean secure) {
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
-
- // We don't have a size yet so pass in 1 for width and height since 0 is invalid
- VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
- INVALID_DISPLAY, null /* Surface */);
- return vd == null ? null : vd.getToken().asBinder();
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
- publicAlternatives = "Use {@code VirtualDisplay#release()} instead.",
- trackingBug = 247078497)
- public static void destroyDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
-
- DisplayManagerGlobal.getInstance().releaseVirtualDisplay(
- IVirtualDisplayCallback.Stub.asInterface(displayToken));
- }
-
- /**
* Returns whether protected content is supported in GPU composition.
* @hide
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbafd1cfc0cd..ec994590e339 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32,6 +32,7 @@ import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_H
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.flags.Flags.viewVelocityApi;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -2309,6 +2310,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
static {
EMPTY_STATE_SET = StateSet.get(0);
@@ -2393,6 +2395,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
| StateSet.VIEW_STATE_PRESSED);
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+ sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
}
/**
@@ -33056,7 +33059,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private float getSizePercentage() {
- if (mResources == null || getVisibility() != VISIBLE) {
+ float alpha = mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
+ int visibility = mViewFlags & VISIBILITY_MASK;
+
+ if (mResources == null || alpha == 0 || visibility != VISIBLE) {
return 0;
}
@@ -33084,22 +33090,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ViewRootImpl viewRootImpl = getViewRootImpl();
float sizePercentage = getSizePercentage();
int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
- if (sToolkitSetFrameRateReadOnlyFlagValue && viewRootImpl != null
- && sizePercentage > 0) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+ if (viewRootImpl != null && sizePercentage > 0) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+ }
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
}
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+ viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+ }
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ viewRootImpl.recordViewPercentage(sizePercentage);
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9f6395e1aab4..1f81a6418962 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -235,6 +235,7 @@ import com.android.internal.policy.PhoneFallbackEventHandler;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.SurfaceCallbackHelper;
+import com.android.modules.expresslog.Counter;
import com.android.window.flags.Flags;
import java.io.IOException;
@@ -252,7 +253,6 @@ import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
-
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
@@ -828,6 +828,8 @@ public final class ViewRootImpl implements ViewParent,
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
+ // The largest view size percentage to the display size. Used on trace to collect metric.
+ private float mLargestChildPercentage = 0.0f;
/**
* The resolved pointer icon type requested by this window.
@@ -1067,6 +1069,7 @@ public final class ViewRootImpl implements ViewParent,
private String mTag = TAG;
private String mFpsTraceName;
+ private String mLargestViewTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1318,6 +1321,7 @@ public final class ViewRootImpl implements ViewParent,
attrs = mWindowAttributes;
setTag();
mFpsTraceName = "FPS of " + getTitle();
+ mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -4739,6 +4743,10 @@ public final class ViewRootImpl implements ViewParent,
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
+
+ long percentage = (long) (mLargestChildPercentage * 100);
+ Trace.setCounter(mLargestViewTraceName, percentage);
+ mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -5059,6 +5067,7 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_FPS) {
trackFPS();
}
+
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
collectFrameRateDecisionMetrics();
}
@@ -9696,6 +9705,9 @@ public final class ViewRootImpl implements ViewParent,
} else {
q.mReceiver.finishInputEvent(q.mEvent, handled);
}
+ if (q.mEvent instanceof KeyEvent) {
+ logHandledSystemKey((KeyEvent) q.mEvent, handled);
+ }
} else {
q.mEvent.recycleIfNeededAfterDispatch();
}
@@ -9703,6 +9715,19 @@ public final class ViewRootImpl implements ViewParent,
recycleQueuedInputEvent(q);
}
+ private void logHandledSystemKey(KeyEvent event, boolean handled) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode != KeyEvent.KEYCODE_STEM_PRIMARY) {
+ return;
+ }
+ if (event.isDown() && event.getRepeatCount() == 0 && handled) {
+ // Initial DOWN event is handled. Log the stem primary key press.
+ Counter.logIncrementWithUid(
+ "input.value_app_handled_stem_primary_key_gestures_count",
+ Process.myUid());
+ }
+ }
+
static boolean isTerminalInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
@@ -12148,7 +12173,8 @@ public final class ViewRootImpl implements ViewParent,
|| motionEventAction == MotionEvent.ACTION_UP;
boolean undesiredType = windowType == TYPE_INPUT_METHOD;
// use toolkitSetFrameRate flag to gate the change
- return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue;
+ return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
+ && getFrameRateBoostOnTouchEnabled();
}
/**
@@ -12223,6 +12249,15 @@ public final class ViewRootImpl implements ViewParent,
return mIsFrameRateBoosting;
}
+ /**
+ * Get the value of mFrameRateBoostOnTouchEnabled
+ * Can be used to checked if touch boost is enabled. The default value is true.
+ */
+ @VisibleForTesting
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ return mWindowAttributes.getFrameRateBoostOnTouchEnabled();
+ }
+
private void boostFrameRate(int boostTimeOut) {
mIsFrameRateBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12252,4 +12287,10 @@ public final class ViewRootImpl implements ViewParent,
void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
mWindowlessBackKeyCallback = callback;
}
+
+ void recordViewPercentage(float percentage) {
+ if (!Trace.isEnabled()) return;
+ // Record the largest view of percentage to the display size.
+ mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+ }
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 87537fbc9961..7bae7ec6559c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -334,6 +334,9 @@ public abstract class Window {
private boolean mOverlayWithDecorCaptionEnabled = true;
private boolean mCloseOnSwipeEnabled = false;
+ private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
// The current window attributes.
@UnsupportedAppUsage
private final WindowManager.LayoutParams mWindowAttributes =
@@ -1373,6 +1376,39 @@ public abstract class Window {
}
/**
+ * Sets whether the frame rate touch boost is enabled for this Window.
+ * When enabled, the frame rate will be boosted when a user touches the Window.
+ *
+ * @param enabled whether the frame rate touch boost is enabled.
+ * @see #getFrameRateBoostOnTouchEnabled()
+ * @see WindowManager.LayoutParams#setFrameRateBoostOnTouchEnabled(boolean)
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setFrameRateBoostOnTouchEnabled(enabled);
+ dispatchWindowAttributesChanged(attrs);
+ }
+ }
+
+ /**
+ * Get whether frame rate touch boost is enabled
+ * {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+ *
+ * @return whether the frame rate touch boost is enabled.
+ * @see #setFrameRateBoostOnTouchEnabled(boolean)
+ * @see WindowManager.LayoutParams#getFrameRateBoostOnTouchEnabled()
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return getAttributes().getFrameRateBoostOnTouchEnabled();
+ }
+ return true;
+ }
+
+ /**
* If {@code isPreferred} is true, this method requests that the connected display does minimal
* post processing when this window is visible on the screen. Otherwise, it requests that the
* display switches back to standard image processing.
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 57a41619ff8d..921afaa99277 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -38,6 +38,8 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.graphics.Insets;
@@ -1519,6 +1521,9 @@ public final class WindowInsets {
}
/** @hide */
+ @TestApi
+ @NonNull
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 07a347ace313..f76822f14189 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4332,6 +4332,13 @@ public interface WindowManager extends ViewManager {
private float mDesiredHdrHeadroom = 0;
/**
+ * For variable refresh rate project.
+ */
+ private boolean mFrameRateBoostOnTouch = true;
+ private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
+ /**
* Carries the requests about {@link WindowInsetsController.Appearance} and
* {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
*
@@ -4766,6 +4773,32 @@ public interface WindowManager extends ViewManager {
}
/**
+ * Set the value whether we should enable Touch Boost
+ *
+ * @param enabled Whether we should enable Touch Boost
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ mFrameRateBoostOnTouch = enabled;
+ }
+ }
+
+ /**
+ * Get the value whether we should enable touch boost as set
+ * by {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+ *
+ * @return A boolean value to indicate whether we should enable touch boost
+ */
+ @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public boolean getFrameRateBoostOnTouchEnabled() {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return mFrameRateBoostOnTouch;
+ }
+ return true;
+ }
+
+ /**
* <p>
* Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
* but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4916,6 +4949,9 @@ public interface WindowManager extends ViewManager {
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
out.writeFloat(mDesiredHdrHeadroom);
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ out.writeBoolean(mFrameRateBoostOnTouch);
+ }
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4988,6 +5024,9 @@ public interface WindowManager extends ViewManager {
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
mDesiredHdrHeadroom = in.readFloat();
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ mFrameRateBoostOnTouch = in.readBoolean();
+ }
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5324,6 +5363,12 @@ public interface WindowManager extends ViewManager {
changes |= LAYOUT_CHANGED;
}
+ if (sToolkitSetFrameRateReadOnlyFlagValue
+ && mFrameRateBoostOnTouch != o.mFrameRateBoostOnTouch) {
+ mFrameRateBoostOnTouch = o.mFrameRateBoostOnTouch;
+ changes |= LAYOUT_CHANGED;
+ }
+
return changes;
}
@@ -5546,6 +5591,11 @@ public interface WindowManager extends ViewManager {
sb.append(prefix).append(" forciblyShownTypes=").append(
WindowInsets.Type.toString(forciblyShownTypes));
}
+ if (sToolkitSetFrameRateReadOnlyFlagValue && mFrameRateBoostOnTouch) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" frameRateBoostOnTouch=");
+ sb.append(mFrameRateBoostOnTouch);
+ }
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
sb.append(prefix).append(" paramsForRotation:");
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 53aed498f110..49a28431ee21 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -5400,13 +5400,10 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final AccessibilityAction ACTION_PREVIOUS_HTML_ELEMENT =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content forward.
*
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
* this element should also add the relevant directional scroll actions of
@@ -5447,12 +5444,10 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final AccessibilityAction ACTION_SCROLL_FORWARD =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content backward.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. Depending on the orientation,
* this element should also add the relevant directional scroll actions of
@@ -5647,48 +5642,40 @@ public class AccessibilityNodeInfo implements Parcelable {
@NonNull public static final AccessibilityAction ACTION_SCROLL_IN_DIRECTION =
new AccessibilityAction(R.id.accessibilityActionScrollInDirection);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content up.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_UP =
new AccessibilityAction(R.id.accessibilityActionScrollUp);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content left.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_LEFT =
new AccessibilityAction(R.id.accessibilityActionScrollLeft);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content down.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
public static final AccessibilityAction ACTION_SCROLL_DOWN =
new AccessibilityAction(R.id.accessibilityActionScrollDown);
+ // TODO(316638728): restore ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT in javadoc
/**
* Action to scroll the node content right.
- * <p>
- * <strong>Arguments:</strong>
- * {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT}. This is an optional argument.
- * </p>
+ *
* <p>The UI element that implements this should send a
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
*/
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index b3359b711989..70d8abe1dc27 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -23,9 +23,6 @@ import android.annotation.AnimRes;
import android.annotation.FlaggedApi;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
@@ -55,18 +52,6 @@ public class AnimationUtils {
private static final int TOGETHER = 0;
private static final int SEQUENTIALLY = 1;
- /**
- * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
- * this change ID enables to use expectedPresentationTime instead of the frameTime
- * for the frame start time .
- *
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM)
- @Overridable
- public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
-
private static boolean sExpectedPresentationTimeFlagValue;
static {
sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ac9ad2dc585d..feccc6bef7a4 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2320,6 +2320,15 @@ public final class InputMethodManager {
* @hide
*/
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+ final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
+ synchronized (mH) {
+ if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+ // Fail early if the view is not focused and not served
+ // to avoid logging many erroneous calls.
+ return false;
+ }
+ }
+
final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
null /* component */, Process.myUid(),
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c7609a6c18af..828ec265f4c8 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -44,9 +44,7 @@ public final class URLUtil {
static final String PROXY_BASE = "file:///cookieless_proxy/";
static final String CONTENT_BASE = "content:";
- /**
- * Cleans up (if possible) user-entered web addresses
- */
+ /** Cleans up (if possible) user-entered web addresses */
public static String guessUrl(String inUrl) {
String retVal = inUrl;
@@ -86,8 +84,12 @@ public final class URLUtil {
return webAddress.toString();
}
- public static String composeSearchUrl(String inQuery, String template,
- String queryPlaceHolder) {
+ /**
+ * Inserts the {@code inQuery} in the {@code template} after URL-encoding it. The encoded query
+ * will replace the {@code queryPlaceHolder}.
+ */
+ public static String composeSearchUrl(
+ String inQuery, String template, String queryPlaceHolder) {
int placeHolderIndex = template.indexOf(queryPlaceHolder);
if (placeHolderIndex < 0) {
return null;
@@ -104,8 +106,7 @@ public final class URLUtil {
return null;
}
- buffer.append(template.substring(
- placeHolderIndex + queryPlaceHolder.length()));
+ buffer.append(template.substring(placeHolderIndex + queryPlaceHolder.length()));
return buffer.toString();
}
@@ -123,8 +124,7 @@ public final class URLUtil {
byte b = url[i];
if (b == '%') {
if (url.length - i > 2) {
- b = (byte) (parseHex(url[i + 1]) * 16
- + parseHex(url[i + 2]));
+ b = (byte) (parseHex(url[i + 1]) * 16 + parseHex(url[i + 2]));
i += 2;
} else {
throw new IllegalArgumentException("Invalid format");
@@ -189,8 +189,8 @@ public final class URLUtil {
}
/**
- * @return {@code true} if the url is a proxy url to allow cookieless network
- * requests from a file url.
+ * @return {@code true} if the url is a proxy url to allow cookieless network requests from a
+ * file url.
* @deprecated Cookieless proxy is no longer supported.
*/
@Deprecated
@@ -202,9 +202,10 @@ public final class URLUtil {
* @return {@code true} if the url is a local file.
*/
public static boolean isFileUrl(String url) {
- return (null != url) && (url.startsWith(FILE_BASE) &&
- !url.startsWith(ASSET_BASE) &&
- !url.startsWith(PROXY_BASE));
+ return (null != url)
+ && (url.startsWith(FILE_BASE)
+ && !url.startsWith(ASSET_BASE)
+ && !url.startsWith(PROXY_BASE));
}
/**
@@ -232,18 +233,18 @@ public final class URLUtil {
* @return {@code true} if the url is an http: url.
*/
public static boolean isHttpUrl(String url) {
- return (null != url) &&
- (url.length() > 6) &&
- url.substring(0, 7).equalsIgnoreCase("http://");
+ return (null != url)
+ && (url.length() > 6)
+ && url.substring(0, 7).equalsIgnoreCase("http://");
}
/**
* @return {@code true} if the url is an https: url.
*/
public static boolean isHttpsUrl(String url) {
- return (null != url) &&
- (url.length() > 7) &&
- url.substring(0, 8).equalsIgnoreCase("https://");
+ return (null != url)
+ && (url.length() > 7)
+ && url.substring(0, 8).equalsIgnoreCase("https://");
}
/**
@@ -271,19 +272,17 @@ public final class URLUtil {
return false;
}
- return (isAssetUrl(url) ||
- isResourceUrl(url) ||
- isFileUrl(url) ||
- isAboutUrl(url) ||
- isHttpUrl(url) ||
- isHttpsUrl(url) ||
- isJavaScriptUrl(url) ||
- isContentUrl(url));
+ return (isAssetUrl(url)
+ || isResourceUrl(url)
+ || isFileUrl(url)
+ || isAboutUrl(url)
+ || isHttpUrl(url)
+ || isHttpsUrl(url)
+ || isJavaScriptUrl(url)
+ || isContentUrl(url));
}
- /**
- * Strips the url of the anchor.
- */
+ /** Strips the url of the anchor. */
public static String stripAnchor(String url) {
int anchorIndex = url.indexOf('#');
if (anchorIndex != -1) {
@@ -293,19 +292,16 @@ public final class URLUtil {
}
/**
- * Guesses canonical filename that a download would have, using
- * the URL and contentDisposition. File extension, if not defined,
- * is added based on the mimetype
+ * Guesses canonical filename that a download would have, using the URL and contentDisposition.
+ * File extension, if not defined, is added based on the mimetype
+ *
* @param url Url to the content
* @param contentDisposition Content-Disposition HTTP header or {@code null}
* @param mimeType Mime-type of the content or {@code null}
- *
* @return suggested filename
*/
public static final String guessFileName(
- String url,
- @Nullable String contentDisposition,
- @Nullable String mimeType) {
+ String url, @Nullable String contentDisposition, @Nullable String mimeType) {
String filename = null;
String extension = null;
@@ -369,8 +365,9 @@ public final class URLUtil {
// Compare the last segment of the extension against the mime type.
// If there's a mismatch, discard the entire extension.
int lastDotIndex = filename.lastIndexOf('.');
- String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- filename.substring(lastDotIndex + 1));
+ String typeFromExt =
+ MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
@@ -389,17 +386,17 @@ public final class URLUtil {
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
- Pattern.CASE_INSENSITIVE);
+ Pattern.compile(
+ "attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/**
- * Parse the Content-Disposition HTTP Header. The format of the header
- * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
- * This header provides a filename for content that is going to be
- * downloaded to the file system. We only support the attachment type.
- * Note that RFC 2616 specifies the filename value must be double-quoted.
- * Unfortunately some servers do not quote the value so to maintain
- * consistent behaviour with other browsers, we allow unquoted values too.
+ * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
+ * content that is going to be downloaded to the file system. We only support the attachment
+ * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately
+ * some servers do not quote the value so to maintain consistent behaviour with other browsers,
+ * we allow unquoted values too.
*/
@UnsupportedAppUsage
static String parseContentDisposition(String contentDisposition) {
@@ -409,7 +406,7 @@ public final class URLUtil {
return m.group(2);
}
} catch (IllegalStateException ex) {
- // This function is defined as returning null when it can't parse the header
+ // This function is defined as returning null when it can't parse the header
}
return null;
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c53489ba3a..d12eda35c745 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@ public abstract class WebSettings {
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@ public abstract class WebSettings {
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5bfa3d759a66..7c9340e72f72 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -165,6 +165,9 @@ public final class TransitionInfo implements Parcelable {
public static final int FLAGS_IS_NON_APP_WINDOW =
FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
+ /** The change will not participate in the animation. */
+ public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4b0191..5705b7ea5cf2 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
+import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -63,6 +64,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
PackageMonitorCallback mPackageMonitorCallback;
+ private Executor mExecutor;
+
@UnsupportedAppUsage
public PackageMonitor() {
final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
if (mPackageMonitorCallback == null) {
PackageManager pm = mRegisteredContext.getPackageManager();
if (pm != null) {
- mPackageMonitorCallback = new PackageMonitorCallback(this,
- new HandlerExecutor(mRegisteredHandler));
+ mExecutor = new HandlerExecutor(mRegisteredHandler);
+ mPackageMonitorCallback = new PackageMonitorCallback(this);
int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
}
@@ -131,6 +134,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
mPackageMonitorCallback = null;
mRegisteredContext = null;
+ mExecutor = null;
}
public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
doHandlePackageEvent(intent);
}
+
+ private void postHandlePackageEvent(Intent intent) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> doHandlePackageEvent(intent));
+ }
+ }
+
/**
* Handle the package related event
* @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+ private final WeakReference<PackageMonitor> mMonitorWeakReference;
- private final PackageMonitor mPackageMonitor;
- private final Executor mExecutor;
-
- PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
- mPackageMonitor = monitor;
- mExecutor = executor;
+ PackageMonitorCallback(PackageMonitor monitor) {
+ mMonitorWeakReference = new WeakReference<>(monitor);
}
@Override
@@ -537,7 +545,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
Log.w(TAG, "No intent is set for PackageMonitorCallback");
return;
}
- mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+ PackageMonitor monitor = mMonitorWeakReference.get();
+ if (monitor != null) {
+ monitor.postHandlePackageEvent(intent);
+ }
}
}
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
index e6d38668335a..3e01730ebab6 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
+++ b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
@@ -13,15 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.test;
+package com.android.internal.pm.parsing;
-import android.content.Context;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import junit.framework.TestCase;
+import java.io.File;
-public class AndroidTestCase extends TestCase {
- protected Context mContext;
- public Context getContext() {
- throw new RuntimeException("[ravenwood] Class Context is not supported yet.");
- }
+/** @hide */
+public interface IPackageCacher {
+
+ /**
+ * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
+ * or {@code null} if no cached result exists.
+ */
+ ParsedPackage getCachedResult(File packageFile, int flags);
+
+ /**
+ * Caches the parse result for {@code packageFile} with flags {@code flags}.
+ */
+ void cacheResult(File packageFile, int flags, ParsedPackage parsed);
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
index b6a08a5a546f..e41329340000 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.server.pm.parsing;
+package com.android.internal.pm.parsing;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
@@ -28,26 +27,20 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.TypedArray;
import android.os.Build;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.permission.PermissionManager;
import android.util.DisplayMetrics;
import android.util.Slog;
-import com.android.internal.compat.IPlatformCompat;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.PackageManagerService;
import java.io.File;
import java.util.List;
-import java.util.Set;
/**
* The v2 of package parsing for use when parsing is initiated in the server and must
@@ -59,50 +52,6 @@ import java.util.Set;
*/
public class PackageParser2 implements AutoCloseable {
- /**
- * For parsing inside the system server but outside of {@link PackageManagerService}.
- * Generally used for parsing information in an APK that hasn't been installed yet.
- *
- * This must be called inside the system process as it relies on {@link ServiceManager}.
- */
- @NonNull
- public static PackageParser2 forParsingFileWithDefaults() {
- IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
- null /* cacheDir */, new Callback() {
- @Override
- public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
- try {
- return platformCompat.isChangeEnabled(changeId, appInfo);
- } catch (Exception e) {
- // This shouldn't happen, but assume enforcement if it does
- Slog.wtf(TAG, "IPlatformCompat query failed", e);
- return true;
- }
- }
-
- @Override
- public boolean hasFeature(String feature) {
- // Assume the device doesn't support anything. This will affect permission parsing
- // and will force <uses-permission/> declarations to include all requiredNotFeature
- // permissions and exclude all requiredFeature permissions. This mirrors the old
- // behavior.
- return false;
- }
-
- @Override
- public Set<String> getHiddenApiWhitelistedApps() {
- return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
- }
-
- @Override
- public Set<String> getInstallConstraintsAllowlist() {
- return SystemConfig.getInstance().getInstallConstraintsAllowlist();
- }
- });
- }
-
private static final String TAG = ParsingUtils.TAG;
private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
@@ -118,12 +67,12 @@ public class PackageParser2 implements AutoCloseable {
private final ThreadLocal<ParseTypeImpl> mSharedResult;
@Nullable
- protected PackageCacher mCacher;
+ protected IPackageCacher mCacher;
- private final ParsingPackageUtils parsingUtils;
+ private final ParsingPackageUtils mParsingUtils;
public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
- @Nullable File cacheDir, @NonNull Callback callback) {
+ @Nullable IPackageCacher cacher, @NonNull Callback callback) {
if (displayMetrics == null) {
displayMetrics = new DisplayMetrics();
displayMetrics.setToDefaults();
@@ -134,9 +83,9 @@ public class PackageParser2 implements AutoCloseable {
List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
.getSplitPermissions();
- mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
+ mCacher = cacher;
- parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
+ mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
callback);
ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
@@ -155,7 +104,7 @@ public class PackageParser2 implements AutoCloseable {
*/
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
- throws PackageManagerException {
+ throws PackageParserException {
var files = packageFile.listFiles();
// Apk directory is directly nested under the current directory
if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -171,9 +120,9 @@ public class PackageParser2 implements AutoCloseable {
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+ ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
- throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+ throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
@@ -201,12 +150,12 @@ public class PackageParser2 implements AutoCloseable {
*/
@AnyThread
public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
- throws PackageManagerException {
+ throws PackageParserException {
ParseInput input = mSharedResult.get().reset();
- ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+ ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input,
packageLite, flags);
if (result.isError()) {
- throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+ throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
return result.getResult().hideAsParsed();
@@ -226,7 +175,7 @@ public class PackageParser2 implements AutoCloseable {
mSharedAppInfo.remove();
}
- public static abstract class Callback implements ParsingPackageUtils.Callback {
+ public abstract static class Callback implements ParsingPackageUtils.Callback {
@Override
public final ParsingPackage startParsingPackage(@NonNull String packageName,
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d18a9ae..56ea52d2ad8b 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@ nativeGetSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager, jobject s
return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
}
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+ jobject sensor, jint index) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+ Vector<Sensor> sensorList;
+ ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+ if (ssize_t(index) >= count) {
+ return false;
+ }
+
+ return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
static void
nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
@@ -539,6 +552,9 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = {
{"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
(void *)nativeGetSensorAtIndex},
+ {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetDefaultDeviceSensorAtIndex},
+
{"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
{"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 052e2f20a313..d3f3af738273 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -610,6 +610,9 @@ message GlobalSettingsProto {
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // This is an optional feature where ringer mode affects alarm stream as well
+ optional SettingProto mute_alarm_stream_with_ringer_mode = 160 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
reserved 147; // Used to be apply_ramping_ringer
message MultiSim {
@@ -1086,5 +1089,5 @@ message GlobalSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 160;
+ // Next tag = 161;
}
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
index e6c6d5978779..118dfc8165d2 100644
--- a/core/proto/android/view/insetssource.proto
+++ b/core/proto/android/view/insetssource.proto
@@ -26,7 +26,7 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSource} object.
*/
message InsetsSourceProto {
- optional string type = 1;
+ optional string type = 1 [deprecated=true];
optional .android.graphics.RectProto frame = 2;
optional .android.graphics.RectProto visible_frame = 3;
optional bool visible = 4;
diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto
index a01ad8eea582..882163f6a9d4 100644
--- a/core/proto/android/view/insetssourceconsumer.proto
+++ b/core/proto/android/view/insetssourceconsumer.proto
@@ -27,11 +27,12 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSourceConsumer} object.
*/
message InsetsSourceConsumerProto {
- optional string internal_insets_type = 1;
+ optional string internal_insets_type = 1 [deprecated=true];
optional bool has_window_focus = 2;
optional bool is_requested_visible = 3;
optional InsetsSourceControlProto source_control = 4;
optional .android.graphics.RectProto pending_frame = 5;
optional .android.graphics.RectProto pending_visible_frame = 6;
optional int32 animation_state = 7;
+ optional int32 type_number = 8;
} \ No newline at end of file
diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto
index 3ac3cbfafaff..afab57f416cf 100644
--- a/core/proto/android/view/insetssourcecontrol.proto
+++ b/core/proto/android/view/insetssourcecontrol.proto
@@ -27,7 +27,8 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSourceControl} object.
*/
message InsetsSourceControlProto {
- optional string type = 1;
+ optional string type = 1 [deprecated=true];
optional .android.graphics.PointProto position = 2;
optional SurfaceControlProto leash = 3;
+ optional int32 type_number = 4;
} \ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 596cfe5a695d..d1143c43ad61 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2986,7 +2986,12 @@
depends on the number of isolated services that an application starts,
and how much memory those services save by preloading and sharing memory with
the app zygote. Therefore, it is recommended to measure memory usage under
- typical workloads to determine whether it makes sense to use this flag. -->
+ typical workloads to determine whether it makes sense to use this flag.
+
+ <p>There is a limit to the number of isolated services that can be spawned from
+ the Application Zygote; the absolute limit is 100, but due to potential
+ delays in service process cleanup, a much safer limit to use in practice is 50.
+ -->
<attr name="useAppZygote" format="boolean" />
<!-- If this is a foreground service, specify its category. -->
<attr name="foregroundServiceType" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d0de5f0e4104..804e9ef8476b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2262,6 +2262,9 @@
<!-- The default min volume for the alarm stream -->
<integer name="config_audio_alarm_min_vol">1</integer>
+ <!-- Flag indicating if ringer mode affects alarm stream -->
+ <bool name="config_audio_ringer_mode_affects_alarm_stream">false</bool>
+
<!-- The default value for whether head tracking for
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -6862,4 +6865,8 @@
<!-- Whether the media player is shown on the quick settings -->
<bool name="config_quickSettingsShowMediaPlayer">true</bool>
+
+ <!-- Defines suitability of the built-in speaker route.
+ Refer to {@link MediaRoute2Info} to see supported values. -->
+ <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 38943306b962..9589fb00fd5d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -288,6 +288,7 @@
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="integer" name="config_audio_alarm_min_vol" />
+ <java-symbol type="bool" name="config_audio_ringer_mode_affects_alarm_stream" />
<java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
@@ -5306,4 +5307,7 @@
<java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
<java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
+
+ <!-- Android MediaRouter framework configs. -->
+ <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
</resources>
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 2b4123af3885..73d7fe952af3 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,6 +16,9 @@
package android.companion;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
+
import android.content.Context;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
@@ -36,16 +39,22 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+/**
+ * Tests that CDM can intake incoming messages in the system data transport and output results.
+ *
+ * Build/Install/Run: atest CompanionTests:SystemDataTransportTest
+ */
public class SystemDataTransportTest extends InstrumentationTestCase {
private static final String TAG = "SystemDataTransportTest";
private static final int MESSAGE_INVALID = 0xF00DCAFE;
-
+ private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++
+ private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
- private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
- private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
@@ -122,8 +131,6 @@ public class SystemDataTransportTest extends InstrumentationTestCase {
new Random().nextBytes(blob);
final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob);
- final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob);
- assertTransportBehavior(input, expected);
}
public void testMultiplePingPing() {
@@ -176,6 +183,43 @@ public class SystemDataTransportTest extends InstrumentationTestCase {
testPingHandRolled();
}
+ public void testInvalidOnewayMessages() throws InterruptedException {
+ // Add a callback
+ final CountDownLatch received = new CountDownLatch(1);
+ mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID,
+ (id, data) -> received.countDown());
+
+ final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1);
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ // Assert that a one-way message was ignored (does not trigger a callback)
+ assertFalse(received.await(5, TimeUnit.SECONDS));
+
+ // There should not be a response to one-way messages
+ assertEquals(0, out.toByteArray().length);
+ }
+
+
+ public void testOnewayMessages() throws InterruptedException {
+ // Add a callback
+ final CountDownLatch received = new CountDownLatch(1);
+ mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING,
+ (id, data) -> received.countDown());
+
+ final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1);
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ // Assert that a one-way message was received
+ assertTrue(received.await(1, TimeUnit.SECONDS));
+
+ // There should not be a response to one-way messages
+ assertEquals(0, out.toByteArray().length);
+ }
+
public static byte[] concat(byte[]... blobs) {
int length = 0;
for (byte[] blob : blobs) {
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 1577d9c1c1ab..5b0502da1bdf 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -1244,29 +1244,33 @@ public class NotificationTest {
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() {
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture((Bitmap) null);
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_pictureIconKeyNull() {
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(bitmap);
@@ -1274,11 +1278,13 @@ public class NotificationTest {
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
- public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() {
+ public void testBigPictureStyle_setExtras_pictureIconTypeIcon_pictureKeyNull() {
Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus);
Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle();
bpStyle.bigPicture(icon);
@@ -1286,7 +1292,9 @@ public class NotificationTest {
Bundle extras = new Bundle();
bpStyle.addExtras(extras);
- assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8308e7c5ef19..1617eda6a77c 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -20,12 +20,15 @@ import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.SparseArray
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
import org.junit.Rule
import kotlin.math.ceil
import kotlin.math.floor
@@ -45,6 +48,19 @@ class FontScaleConverterFactoryTest {
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+
+ @Before
+ fun setup() {
+ defaultLookupTables = FontScaleConverterFactory.sLookupTables.clone()
+ }
+
+ @After
+ fun teardown() {
+ // Restore the default tables (since some tests will have added extras to the cache)
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ }
+
@Test
fun scale200IsTwiceAtSmallSizes() {
val table = FontScaleConverterFactory.forScale(2F)!!
@@ -245,7 +261,7 @@ class FontScaleConverterFactoryTest {
}
companion object {
- private const val CONVERSION_TOLERANCE = 0.05f
+ private const val CONVERSION_TOLERANCE = 0.18f
}
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b30a0c8fbbd3..cf3eb12498ca 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -659,8 +659,6 @@ public class ViewRootImplTest {
ViewRootImpl viewRootImpl = view.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
view.invalidate();
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL);
viewRootImpl.notifyInsetsAnimationRunningStateChanged(true);
view.invalidate();
});
@@ -672,6 +670,37 @@ public class ViewRootImplTest {
});
}
+
+ /**
+ * Test FrameRateBoostOnTouchEnabled API
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_frameRateBoostOnTouch() {
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
+ assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
+ assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+ attrs.getFrameRateBoostOnTouchEnabled());
+
+ sInstrumentation.runOnMainSync(() -> {
+ attrs.setFrameRateBoostOnTouchEnabled(false);
+ viewRootImpl.setLayoutParams(attrs, false);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ sInstrumentation.runOnMainSync(() -> {
+ final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
+ assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
+ assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+ newAttrs.getFrameRateBoostOnTouchEnabled());
+ });
+ }
+
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87878b7..000000000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d9393c0b8..000000000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187ead31f..000000000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +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.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 000000000000..bb7d63edbebd
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_NonPlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ manifest: "static/AndroidManifest.xml",
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.signature_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde925e4..000000000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 000000000000..ee0c0e526c0a
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_UpdateOverlay",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ static_libs: ["androidx.test.rules"],
+ aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["framework/v1/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["framework/v2/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["app/v1/res"],
+ manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["app/v2/res"],
+ manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887e9493..000000000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f64a82d..13d38d2ff2c6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
- <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
- otherwise, it may not be able to enforce provision for managed devices. -->
+ <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+ when app in background; otherwise, it may not be able to enforce provision for managed
+ devices. -->
<allow-in-power-save package="com.android.devicelockcontroller" />
+ <allow-in-data-usage-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f55c07..b9efe65d2754 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@ applications that come with the platform
<permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
<permission name="android.permission.BIND_WALLPAPER"/>
<permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
</privapp-permissions>
<privapp-permissions package="com.android.dynsystem">
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
deleted file mode 100644
index ff49cdcab349..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static java.util.Objects.requireNonNull;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * The parameter to create an overlay container that retrieved from
- * {@link android.app.ActivityOptions} bundle.
- */
-class OverlayCreateParams {
-
- // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS =
- "androidx.window.extensions.OverlayCreateParams";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
- "androidx.window.extensions.OverlayCreateParams.taskId";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
- "androidx.window.extensions.OverlayCreateParams.tag";
-
- @VisibleForTesting
- static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
- "androidx.window.extensions.OverlayCreateParams.bounds";
-
- private final int mTaskId;
-
- @NonNull
- private final String mTag;
-
- @NonNull
- private final Rect mBounds;
-
- OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
- mTaskId = taskId;
- mTag = requireNonNull(tag);
- mBounds = requireNonNull(bounds);
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- @NonNull
- String getTag() {
- return mTag;
- }
-
- @NonNull
- Rect getBounds() {
- return mBounds;
- }
-
- @Override
- public int hashCode() {
- int result = mTaskId;
- result = 31 * result + mTag.hashCode();
- result = 31 * result + mBounds.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) return true;
- if (!(obj instanceof OverlayCreateParams thatParams)) return false;
- return mTaskId == thatParams.mTaskId
- && mTag.equals(thatParams.mTag)
- && mBounds.equals(thatParams.mBounds);
- }
-
- @Override
- public String toString() {
- return OverlayCreateParams.class.getSimpleName() + ": {"
- + "taskId=" + mTaskId
- + ", tag=" + mTag
- + ", bounds=" + mBounds
- + "}";
- }
-
- /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
- @Nullable
- static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
- final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
- if (paramsBundle == null) {
- return null;
- }
- final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
- final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
- final Rect bounds = requireNonNull(paramsBundle.getParcelable(
- KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
-
- return new OverlayCreateParams(taskId, tag, bounds);
- }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4973a4d85af7..15ee4e1d4adf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -34,6 +34,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHA
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
@@ -136,6 +137,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
/**
+ * A calculator function to compute {@link ActivityStack} attributes in a task, which is called
+ * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ mActivityStackAttributesCalculator;
+
+ /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
* When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -319,6 +329,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void setActivityStackAttributesCalculator(
+ @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+ calculator) {
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = calculator;
+ }
+ }
+
+ @Override
+ public void clearActivityStackAttributesCalculator() {
+ synchronized (mLock) {
+ mActivityStackAttributesCalculator = null;
+ }
+ }
+
@GuardedBy("mLock")
@Nullable
Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
@@ -1412,7 +1438,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
- null /* overlayTag */);
+ null /* overlayTag */, null /* launchOptions */);
}
/**
@@ -1426,7 +1452,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag) {
+ @Nullable String overlayTag, @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1443,7 +1469,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
- intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
+ launchOptions);
final IBinder taskFragmentToken = container.getTaskFragmentToken();
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
@@ -1570,14 +1597,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+ null /* launchOptions */);
}
@GuardedBy("mLock")
@@ -1585,7 +1614,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull Activity activityInTask, int taskId,
@NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
+ null /* launchOptions */);
}
/**
@@ -1602,11 +1632,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param overlayTag The tag for the new created overlay container. It must be
* needed if {@code isOverlay} is {@code true}. Otherwise,
* it should be {@code null}.
+ * @param launchOptions The launch options bundle to create a container. Must be
+ * specified for overlay container.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1615,7 +1648,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
+ launchOptions);
return container;
}
@@ -2345,28 +2379,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
@Nullable
TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull WindowContainerTransaction wct,
- @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
@NonNull Intent intent, @NonNull Activity launchActivity) {
- final int taskId = overlayCreateParams.getTaskId();
- if (taskId != launchTaskId) {
- // The task ID doesn't match the launch activity's. Cannot determine the host task
- // to launch the overlay.
- throw new IllegalArgumentException("The task ID of "
- + "OverlayCreateParams#launchingActivity must match the task ID of "
- + "the activity to #startActivity with the activity options that takes "
- + "OverlayCreateParams.");
- }
final List<TaskFragmentContainer> overlayContainers =
getAllOverlayTaskFragmentContainers();
- final String overlayTag = overlayCreateParams.getTag();
+ final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
- final Rect bounds = overlayCreateParams.getBounds();
- final Size minDimensions = getMinDimensions(intent);
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
- minDimensions);
+ final ActivityStackAttributesCalculatorParams params =
+ new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo(
+ mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+ // Fallback to expand the bounds if there's no activityStackAttributes calculator.
+ final Rect relativeBounds = mActivityStackAttributesCalculator != null
+ ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
+ : new Rect();
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
+ getMinDimensions(intent));
+ // Expand the bounds if the requested bounds are smaller than minimum dimensions.
+ if (shouldExpandContainer) {
+ relativeBounds.setEmpty();
+ }
+ final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
if (!overlayTag.equals(overlayContainer.getOverlayTag())
@@ -2390,7 +2424,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
.getTaskMetrics().getBounds();
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
!sanitizedBounds.isEmpty());
@@ -2402,8 +2436,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
}
- return createEmptyContainer(wct, intent, taskId,
- (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ // Launch the overlay container to the task with taskId.
+ return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+ options);
}
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2568,12 +2603,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- final OverlayCreateParams overlayCreateParams =
- OverlayCreateParams.fromBundle(options);
+ final String overlayTag = options.getString(KEY_OVERLAY_TAG);
if (Flags.activityEmbeddingOverlayPresentationFlag()
- && overlayCreateParams != null) {
+ && overlayTag != null) {
launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
- overlayCreateParams, taskId, intent, launchingActivity);
+ options, intent, launchingActivity);
} else {
launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
launchingActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b5c32bbe78fa..acfd8e4314bf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -1084,4 +1084,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
return getTaskProperties(activity).getTaskMetrics();
}
+
+ @NonNull
+ ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
+ final Configuration configuration = taskProperties.getConfiguration();
+ final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
+ .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+ configuration.windowConfiguration);
+ return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
+ windowLayoutInfo);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index ed8c4f31306b..afd554b6e52b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -24,6 +24,7 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.util.Size;
import android.window.TaskFragmentAnimationParams;
@@ -105,6 +106,13 @@ class TaskFragmentContainer {
@Nullable
private final String mOverlayTag;
+ /**
+ * The launch options that was used to create this container. Must not be {@code null} for
+ * {@link #isOverlay()} container.
+ */
+ @Nullable
+ private final Bundle mLaunchOptions;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -165,7 +173,7 @@ class TaskFragmentContainer {
/**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
- * TaskFragmentContainer, String)
+ * TaskFragmentContainer, String, Bundle)
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent,
@@ -173,7 +181,8 @@ class TaskFragmentContainer {
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
- controller, pairedPrimaryContainer, null /* overlayTag */);
+ controller, pairedPrimaryContainer, null /* overlayTag */,
+ null /* launchOptions */);
}
/**
@@ -181,11 +190,14 @@ class TaskFragmentContainer {
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
* @param overlayTag Sets to indicate this taskFragment is an overlay container
+ * @param launchOptions The launch options to create this container. Must not be
+ * {@code null} for an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -195,6 +207,10 @@ class TaskFragmentContainer {
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
mOverlayTag = overlayTag;
+ if (overlayTag != null) {
+ Objects.requireNonNull(launchOptions);
+ }
+ mLaunchOptions = launchOptions;
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
@@ -344,9 +360,7 @@ class TaskFragmentContainer {
if (activities == null) {
return null;
}
- // Already checked nullity in collectNonFinishingActivities.
- final Rect bounds = getInfo().getConfiguration().windowConfiguration.getBounds();
- return new ActivityStack(activities, isEmpty(), mToken, bounds, mOverlayTag);
+ return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag);
}
/** Adds the activity that will be reparented to this container. */
@@ -903,8 +917,8 @@ class TaskFragmentContainer {
}
/**
- * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
- * taskFragment container is not an overlay container.
+ * Returns the tag specified in launch options. {@code null} if this taskFragment container is
+ * not an overlay container.
*/
@Nullable
String getOverlayTag() {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4c2433fab2f8..678bdef3df92 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -18,14 +18,11 @@ package androidx.window.extensions.embedding;
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -45,6 +42,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -98,9 +96,6 @@ public class OverlayPresentationTest {
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
- private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
- new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
-
private SplitController.ActivityStartMonitor mMonitor;
private Intent mIntent;
@@ -165,37 +160,15 @@ public class OverlayPresentationTest {
}
@Test
- public void testOverlayCreateParamsFromBundle() {
- assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
-
- assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
- .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
- }
-
- @Test
public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
- mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+ final Bundle optionsBundle = ActivityOptions.makeBasic().toBundle();
+ optionsBundle.putString(KEY_OVERLAY_TAG, "test");
+ mMonitor.onStartActivity(mActivity, mIntent, optionsBundle);
verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
- anyInt(), any(), any());
- }
-
- @NonNull
- private static Bundle createOverlayCreateParamsTestBundle() {
- final Bundle bundle = new Bundle();
-
- final Bundle paramsBundle = new Bundle();
- paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
- TEST_OVERLAY_CREATE_PARAMS.getTaskId());
- paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
- paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
- TEST_OVERLAY_CREATE_PARAMS.getBounds());
-
- bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
-
- return bundle;
+ any(), any());
}
@Test
@@ -221,19 +194,11 @@ public class OverlayPresentationTest {
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
- assertThrows("The method must return null due to task mismatch between"
- + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
- () -> createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
- }
-
- @Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test3");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched to the same task")
@@ -245,9 +210,9 @@ public class OverlayPresentationTest {
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
- TASK_ID + 2);
+ doReturn(TASK_ID + 2).when(mActivity).getTaskId();
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test1");
assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ " is launched with the same tag as an existing overlay container in a different "
@@ -261,9 +226,10 @@ public class OverlayPresentationTest {
createExistingOverlayContainers();
final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test1", bounds),
- TASK_ID);
+ "test1");
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -279,9 +245,8 @@ public class OverlayPresentationTest {
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
createExistingOverlayContainers();
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
- TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test2");
// OverlayContainer1 is dismissed since new container is launched in the same task with
// different tag. OverlayContainer2 is dismissed since new container is launched with the
@@ -304,8 +269,11 @@ public class OverlayPresentationTest {
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -316,7 +284,7 @@ public class OverlayPresentationTest {
// Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -329,11 +297,11 @@ public class OverlayPresentationTest {
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
- "test", bounds);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- paramsOutsideTaskBounds, TASK_ID);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -344,7 +312,7 @@ public class OverlayPresentationTest {
// Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -355,15 +323,17 @@ public class OverlayPresentationTest {
@Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
- assertThat(overlayContainer
- .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
- assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo("test");
}
@Test
@@ -416,12 +386,14 @@ public class OverlayPresentationTest {
@Test
public void testGetTopNonFinishingActivityWithOverlay() {
- createTestOverlayContainer(TASK_ID, "test1");
+ TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1");
+
final Activity activity = createMockActivity();
final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
final TaskContainer task = container.getTaskContainer();
- assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+ assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */))
+ .isEqualTo(overlayContainer.getTopNonFinishingActivity());
assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
}
@@ -458,10 +430,11 @@ public class OverlayPresentationTest {
* #createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@Nullable
- private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
- @NonNull OverlayCreateParams params, int taskId) {
- return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
- taskId, mIntent, mActivity);
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+ launchOptions, mIntent, mActivity);
}
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -475,10 +448,11 @@ public class OverlayPresentationTest {
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ Activity activity = createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
- null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
- null /* pairedPrimaryContainer */, tag);
- setupTaskFragmentInfo(overlayContainer, mActivity);
+ null /* pendingAppearedActivity */, mIntent, activity, taskId,
+ null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
+ setupTaskFragmentInfo(overlayContainer, activity);
return overlayContainer;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 8c274a26177d..bab4e9195880 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -590,7 +590,7 @@ public class SplitControllerTest {
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
}
@Test
@@ -753,7 +753,7 @@ public class SplitControllerTest {
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -796,7 +796,7 @@ public class SplitControllerTest {
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString());
+ anyString(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4511f3b91c5c..901d5fa0cd9a 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -57,3 +57,10 @@ flag {
description: "Enables left/right split in portrait"
bug: "291018646"
}
+
+flag {
+ name: "enable_new_bubble_animations"
+ namespace: "multitasking"
+ description: "Enables new animations for expand and collapse for bubbles"
+ bug: "311450609"
+}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1e4dca..e4f793c2665b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="16dp">
-
+ android:paddingStart="6dp"
+ android:paddingEnd="8dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="20dp"
- android:minWidth="80dp"
+ android:maxWidth="86dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de6168bc7..0a40cea3134d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+ <!-- Required empty space to be visible for partially offscreen tasks. -->
+ <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+ <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+ <!-- 32dp width back button + 10dp margin -->
+ <dimen name="caption_left_buttons_width">32dp</dimen>
+
+ <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+ <dimen name="caption_right_buttons_width">126dp</dimen>
+
+ <!-- 2 buttons * 48dp button size. -->
+ <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+ <!-- 22dp padding + 24dp app icon + 16dp expand button.
+ Text varies in size, we will calculate that width separately. -->
+ <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b7f749e8a8b6..470a82511481 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1291,7 +1291,7 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
+ final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1342,7 +1342,7 @@ public class BubbleStackView extends FrameLayout
// We only show user education for conversation bubbles right now
return false;
}
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
+ final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -2323,7 +2323,8 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
updatePointerPosition(false /* forIme */);
mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
updateOverflowDotVisibility(true /* expanding */);
@@ -2384,7 +2385,7 @@ public class BubbleStackView extends FrameLayout
}
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- if (mExpandedBubble.getExpandedView() != null) {
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView().setContentAlpha(0f);
mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 61e17c8ec459..da71b1c741bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -33,15 +33,16 @@ import com.android.wm.shell.animation.Interpolators
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
-
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
+class ManageEducationView(
+ context: Context,
+ private val positioner: BubblePositioner
+) : LinearLayout(context) {
+
+ companion object {
+ const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ }
- private val positioner: BubblePositioner = positioner
private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
@@ -128,7 +129,7 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateManageEducationSeen()
}
/**
@@ -218,13 +219,11 @@ class ManageEducationView(context: Context, positioner: BubblePositioner) : Line
}
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateManageEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .putBoolean(PREF_MANAGED_EDUCATION, true)
.apply()
}
}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 2cabb65abe7a..95f101722e89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -34,19 +34,15 @@ import com.android.wm.shell.animation.Interpolators
*/
class StackEducationView(
context: Context,
- positioner: BubblePositioner,
- controller: BubbleController
+ private val positioner: BubblePositioner,
+ private val controller: BubbleController
) : LinearLayout(context) {
- private val TAG =
- if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
-
- private val positioner: BubblePositioner = positioner
- private val controller: BubbleController = controller
+ companion object {
+ const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
+ private const val ANIMATE_DURATION: Long = 200
+ private const val ANIMATE_DURATION_SHORT: Long = 40
+ }
private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
@@ -175,7 +171,7 @@ class StackEducationView(
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
}
- setShouldShow(false)
+ updateStackEducationSeen()
return true
}
@@ -196,13 +192,11 @@ class StackEducationView(
.withEndAction { visibility = GONE }
}
- private fun setShouldShow(shouldShow: Boolean) {
+ private fun updateStackEducationSeen() {
context
.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
.edit()
- .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .putBoolean(PREF_STACK_EDUCATION, true)
.apply()
}
}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f794fef48f27..893a87fe4885 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -48,12 +48,14 @@ public class BubbleBarAnimationHelper {
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100;
+ private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
+ private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 150;
/**
* Additional scale applied to expanded view when it is positioned inside a magnetic target.
*/
- private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f;
- private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
- private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+ private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.6f;
+ private static final float EXPANDED_VIEW_DRAG_SCALE = 0.5f;
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -72,6 +74,7 @@ public class BubbleBarAnimationHelper {
private final Context mContext;
private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
+ private final int[] mTmpLocation = new int[2];
private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded = false;
@@ -220,6 +223,25 @@ public class BubbleBarAnimationHelper {
}
/**
+ * Animate the expanded bubble when it is being dragged
+ */
+ public void animateStartDrag() {
+ final BubbleBarExpandedView bbev = getExpandedView();
+ if (bbev == null) {
+ Log.w(TAG, "Trying to animate start drag without a bubble");
+ return;
+ }
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0f);
+ bbev.animate()
+ .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+ .scaleY(EXPANDED_VIEW_DRAG_SCALE)
+ .setInterpolator(Interpolators.EMPHASIZED)
+ .setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION)
+ .start();
+ }
+
+ /**
* Animates dismissal of currently expanded bubble
*
* @param endRunnable a runnable to run at the end of the animation
@@ -261,7 +283,10 @@ public class BubbleBarAnimationHelper {
.setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION)
.setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
.withStartAction(() -> bbev.setAnimating(true))
- .withEndAction(() -> bbev.setAnimating(false))
+ .withEndAction(() -> {
+ bbev.setAnimating(false);
+ bbev.resetPivot();
+ })
.start();
}
@@ -277,25 +302,52 @@ public class BubbleBarAnimationHelper {
Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
return;
}
- Point expandedViewCenter = getViewCenterOnScreen(bbev);
-
- // Calculate the difference between the target's center coordinates and the object's.
- // Animating the object's x/y properties by these values will center the object on top
- // of the magnetic target.
- float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x;
- float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y;
// Calculate scale of expanded view so it fits inside the magnetic target
float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight());
- float targetMaxSide = Math.max(target.getTargetView().getWidth(),
- target.getTargetView().getHeight());
- float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide;
+ View targetView = target.getTargetView();
+ float targetMaxSide = Math.max(targetView.getWidth(), targetView.getHeight());
+ // Reduce target size to have some padding between the target and expanded view
+ targetMaxSide *= EXPANDED_VIEW_IN_TARGET_SCALE;
+ float scaleInTarget = targetMaxSide / bbevMaxSide;
+
+ // Scale around the top center of the expanded view. Same as when dragging.
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(0);
+
+ // When the view animates into the target, it is scaled down with the pivot at center top.
+ // Find the point on the view that would be the center of the view at its final scale.
+ // Once we know that, we can calculate x and y distance from the center of the target view
+ // and use that for the translation animation to ensure that the view at final scale is
+ // placed at the center of the target.
+
+ // Set mTmpLocation to the current location of the view on the screen, taking into account
+ // any scale applied.
+ bbev.getLocationOnScreen(mTmpLocation);
+ // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+ // x-axis will be the same as the center of the view at current size.
+ // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+ // center of the view at its current size.
+ float currentWidth = bbev.getWidth() * bbev.getScaleX();
+ mTmpLocation[0] += currentWidth / 2;
+ // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+ // remains the same.
+ // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+ // moved down by half of the height at final scale.
+ float targetHeight = bbev.getHeight() * scaleInTarget;
+ mTmpLocation[1] += targetHeight / 2;
+ // mTmpLocation is now set to the point on the view that will be the center of the view once
+ // scale is applied.
+
+ // Calculate the difference between the target's center coordinates and mTmpLocation
+ float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+ float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
bbev.animate()
.translationX(bbev.getTranslationX() + xDiff)
.translationY(bbev.getTranslationY() + yDiff)
- .scaleX(scale)
- .scaleY(scale)
+ .scaleX(scaleInTarget)
+ .scaleY(scaleInTarget)
.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
.setInterpolator(Interpolators.EMPHASIZED)
.withStartAction(() -> bbev.setAnimating(true))
@@ -319,8 +371,8 @@ public class BubbleBarAnimationHelper {
}
expandedView
.animate()
- .scaleX(1f)
- .scaleY(1f)
+ .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+ .scaleY(EXPANDED_VIEW_DRAG_SCALE)
.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
.setInterpolator(Interpolators.EMPHASIZED)
.withStartAction(() -> expandedView.setAnimating(true))
@@ -385,12 +437,4 @@ public class BubbleBarAnimationHelper {
final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
return new Size(width, height);
}
-
- private Point getViewCenterOnScreen(View view) {
- Point center = new Point();
- int[] onScreenLocation = view.getLocationOnScreen();
- center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f));
- center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f));
- return center;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index d21545079cc2..5e634a23955a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -74,6 +74,9 @@ class BubbleBarExpandedViewDragController(
}
private inner class HandleDragListener : RelativeTouchListener() {
+
+ private var isMoving = false
+
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
return !expandedView.isAnimating
@@ -87,6 +90,10 @@ class BubbleBarExpandedViewDragController(
dx: Float,
dy: Float
) {
+ if (!isMoving) {
+ isMoving = true
+ animationHelper.animateStartDrag()
+ }
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
@@ -114,6 +121,7 @@ class BubbleBarExpandedViewDragController(
animationHelper.animateToRestPosition()
dismissView.hide()
}
+ isMoving = false
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba3b7d2..af31f5f27fe9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@ import com.android.wm.shell.util.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@ public class HomeTransitionObserver implements TransitionObserver,
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c68da27..72fba3bb7de4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@ import android.window.RemoteTransition;
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index af69b5272ad5..b0d8b47b170a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -757,6 +757,10 @@ public class Transitions implements RemoteCallable<Transitions>,
}
if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
allOccluded = false;
+ } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+ // Remove the change because it should be invisible in the animation.
+ info.getChanges().remove(i);
+ continue;
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b3772e..6e7d11d9082b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -34,6 +35,7 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
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 6ec91e0e28dd..3b6be8afb9e7 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
@@ -443,6 +443,66 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c733fe3..677c7f1fb5a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@ public class DragPositioningCallbackUtility {
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+ }
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e201b2..5d006fb4d9e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0caaca5..4363558ca00b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b7558c7d8..ee0e31ec3aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -178,6 +179,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a8134c2d3..144373f3550e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04aecf6c..e60be7186b1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@ class DragPositioningCallbackUtilityTest {
}
@Test
+ fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100)
+
+ DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ validDragArea)
+ assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+ assertThat(repositionTaskBounds.right)
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ assertThat(repositionTaskBounds.bottom)
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ }
+
+ @Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2ee8b3..2ce49cf62614 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -660,6 +661,38 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf14324a..a759b53f4238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -379,6 +380,38 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e42f74b8d17..fe508e23af33 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -702,6 +702,11 @@ public class WindowDecorationTests extends ShellTestCase {
relayout(taskInfo, false /* applyStartTransactionOnDraw */);
}
+ @Override
+ Rect calculateValidDragArea() {
+ return null;
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000b6276..67f47756984f 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@ package android.location;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,6 +40,23 @@ import java.util.function.Predicate;
* @hide
*/
public final class LocationResult implements Parcelable {
+ private static final String TAG = "LocationResult";
+
+ // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+ // could likely be lower, but we only want to throw out really absurd values.
+ private static final float MAX_ACCURACY_M = 1000000;
+
+ // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+ // current fastest private jet). Higher speed than the value is considered as a malfunction
+ // than a correct reading.
+ private static final float MAX_SPEED_MPS = 343;
+
+ /** Exception representing an invalid location within a {@link LocationResult}. */
+ public static class BadLocationException extends Exception {
+ public BadLocationException(String message) {
+ super(message);
+ }
+ }
/**
* Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@ public final class LocationResult implements Parcelable {
*
* @hide
*/
- public @NonNull LocationResult validate() {
+ public @NonNull LocationResult validate() throws BadLocationException {
long prevElapsedRealtimeNs = 0;
final int size = mLocations.size();
for (int i = 0; i < size; ++i) {
Location location = mLocations.get(i);
- if (!location.isComplete()) {
- throw new IllegalArgumentException(
- "incomplete location at index " + i + ": " + mLocations);
- }
- if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
- throw new IllegalArgumentException(
- "incorrectly ordered location at index " + i + ": " + mLocations);
+ if (Flags.locationValidation()) {
+ if (location.getLatitude() < -90.0
+ || location.getLatitude() > 90.0
+ || location.getLongitude() < -180.0
+ || location.getLongitude() > 180.0
+ || Double.isNaN(location.getLatitude())
+ || Double.isNaN(location.getLongitude())) {
+ throw new BadLocationException("location must have valid lat/lng");
+ }
+ if (!location.hasAccuracy()) {
+ throw new BadLocationException("location must have accuracy");
+ }
+ if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+ throw new BadLocationException("location must have reasonable accuracy");
+ }
+ if (location.getTime() < 0) {
+ throw new BadLocationException("location must have valid time");
+ }
+ if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+ throw new BadLocationException(
+ "location must have valid monotonically increasing realtime");
+ }
+ if (location.getElapsedRealtimeNanos()
+ > SystemClock.elapsedRealtimeNanos()) {
+ throw new BadLocationException("location must not have realtime in the future");
+ }
+ if (!location.isMock()) {
+ if (location.getProvider() == null) {
+ throw new BadLocationException("location must have valid provider");
+ }
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ throw new BadLocationException("location must not be at 0,0");
+ }
+ }
+
+ if (location.hasSpeed() && (location.getSpeed() < 0
+ || location.getSpeed() > MAX_SPEED_MPS)) {
+ Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+ location.removeSpeed();
+ }
+ } else {
+ if (!location.isComplete()) {
+ throw new IllegalArgumentException(
+ "incomplete location at index " + i + ": " + mLocations);
+ }
+ if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+ throw new IllegalArgumentException(
+ "incorrectly ordered location at index " + i + ": " + mLocations);
+ }
}
prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index b6055e818f8c..a8464d3f86ec 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -20,3 +20,17 @@ flag {
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
}
+
+flag {
+ name: "release_supl_connection_on_timeout"
+ namespace: "location"
+ description: "Flag for releasing SUPL connection on timeout"
+ bug: "315024652"
+}
+
+flag {
+ name: "location_validation"
+ namespace: "location"
+ description: "Flag for location validation"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3dfd5726455d..a5a69f987113 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -22,6 +22,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.media.audio.Flags.autoPublicVolumeApiHardening;
import static android.media.audio.Flags.automaticBtDeviceType;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -5120,6 +5121,71 @@ public class AudioManager {
}
/**
+ * Notifies an application with a focus listener of gain or loss of audio focus
+ *
+ * <p>This is similar to {@link #dispatchAudioFocusChange(AudioFocusInfo, int, AudioPolicy)} but
+ * with additional functionality of fade. The players of the application with audio focus
+ * change, provided they meet the active {@link FadeManagerConfiguration} requirements, are
+ * faded before dispatching the callback to the application. For example, players of the
+ * application losing audio focus will be faded out, whereas players of the application gaining
+ * audio focus will be faded in, if needed.
+ *
+ * <p>The applicability of fade is decided against the supplied active {@link AudioFocusInfo}.
+ * This list cannot be {@code null}. The list can be empty if no other active
+ * {@link AudioFocusInfo} available at the time of the dispatch.
+ *
+ * <p>The {@link FadeManagerConfiguration} supplied here is prioritized over existing fade
+ * configurations. If none supplied, either the {@link FadeManagerConfiguration} set through
+ * {@link AudioPolicy} or the default will be used to determine the fade properties.
+ *
+ * <p>This method can only be used by owners of an {@link AudioPolicy} configured with
+ * {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
+ *
+ * @param afi the recipient of the focus change, that has previously requested audio focus, and
+ * that was received by the {@code AudioPolicy} through
+ * {@link AudioPolicy.AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}
+ * @param focusChange one of focus gain types ({@link #AUDIOFOCUS_GAIN},
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or
+ * {@link #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE})
+ * or one of the focus loss types ({@link AudioManager#AUDIOFOCUS_LOSS},
+ * {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT},
+ * or {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}).
+ * <br>For the focus gain, the change type should be the same as the app requested
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ * @param otherActiveAfis active {@link AudioFocusInfo} that are granted audio focus at the time
+ * of dispatch
+ * @param transientFadeMgrConfig {@link FadeManagerConfiguration} that will be used for fading
+ * players resulting from this dispatch. This is a transient configuration that is only
+ * valid for this focus change and shall be discarded after processing this request.
+ * @return {@link #AUDIOFOCUS_REQUEST_FAILED} if the focus client didn't have a listener or if
+ * there was an error sending the request, or {@link #AUDIOFOCUS_REQUEST_GRANTED} if the
+ * dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
+ * the request was successful but the dispatch of focus change was delayed due to a fade
+ * operation.
+ * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
+ * other active {@link AudioFocusInfo} are {@code null}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
+ @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
+ @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(ap, "AudioPolicy cannot be null");
+ Objects.requireNonNull(otherActiveAfis, "Other active AudioFocusInfo list cannot be null");
+
+ IAudioService service = getService();
+ try {
+ return service.dispatchFocusChangeWithFade(afi, focusChange, ap.cb(), otherActiveAfis,
+ transientFadeMgrConfig);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Used internally by telephony package to abandon audio focus, typically after a call or
* when ringing ends and the call is rejected or not answered.
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 337d4b0a916c..40b0e3e03ef6 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -16,12 +16,13 @@
package android.media;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -93,11 +94,9 @@ import java.util.Objects;
* Helps with recreating a new instance from another to simply change/add on top of the
* existing ones</li>
* </ul>
- * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
- *
* @hide
*/
-
+@SystemApi
@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
public final class FadeManagerConfiguration implements Parcelable {
@@ -523,6 +522,7 @@ public final class FadeManagerConfiguration implements Parcelable {
*
* @param fadeState one of the fade state in {@link FadeStateEnum}
* @return human-readable string
+ * @hide
*/
@NonNull
public static String fadeStateToString(@FadeStateEnum int fadeState) {
@@ -712,7 +712,8 @@ public final class FadeManagerConfiguration implements Parcelable {
*
* <p><b>Notes:</b>
* <ul>
- * <li>When fade state is set to enabled, the builder expects at least one valid usage to be
+ * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
+ * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
* set/added. Failure to do so will result in an exception during {@link #build()}</li>
* <li>Every usage added to the fadeable list should have corresponding volume shaper
* configs defined. This can be achieved by setting either the duration or volume shaper
@@ -720,8 +721,8 @@ public final class FadeManagerConfiguration implements Parcelable {
* {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
* <li> It is recommended to set volume shaper configurations individually for fade out and
* fade in</li>
- * <li>For any incomplete volume shaper configs a volume shaper configuration will be
- * created using either the default fade durations or the ones provided as part of the
+ * <li>For any incomplete volume shaper configurations, a volume shaper configuration will
+ * be created using either the default fade durations or the ones provided as part of the
* {@link #Builder(long, long)}</li>
* <li>Additional volume shaper configs can also configured for a given usage
* with additional attributes like content-type in order to achieve finer fade controls.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index a52f0b08330d..5c268d4ab652 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -398,6 +399,14 @@ interface IAudioService {
int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,
in IAudioPolicyCallback pcb);
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int dispatchFocusChangeWithFade(in AudioFocusInfo afi,
+ in int focusChange,
+ in IAudioPolicyCallback pcb,
+ in List<AudioFocusInfo> otherActiveAfis,
+ in FadeManagerConfiguration transientFadeMgrConfig);
+
oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
@EnforcePermission("BLUETOOTH_STACK")
@@ -754,4 +763,16 @@ interface IAudioService {
oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int setFadeManagerConfigurationForFocusLoss(in FadeManagerConfiguration fmcForFocusLoss);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ int clearFadeManagerConfigurationForFocusLoss();
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss();
}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index 29bfd1acae17..e2dddad274e1 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -19,6 +19,7 @@ package android.media;
import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
+import android.os.UserHandle;
/**
* @hide
@@ -35,5 +36,6 @@ oneway interface IMediaRouter2 {
* Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
*/
void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
- in MediaRoute2Info route);
+ in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle,
+ in String transferInitiatorPackageName);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index fa4d1a1ff935..04e99ea8a57f 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -64,7 +64,8 @@ interface IMediaRouterService {
void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
in RoutingSessionInfo oldSession, in MediaRoute2Info route,
- in @nullable Bundle sessionHints);
+ in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle,
+ in String transferInitiatorPackageName);
void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -84,13 +85,16 @@ interface IMediaRouterService {
void stopScan(IMediaRouter2Manager manager);
void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
- in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
+ in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
+ in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName);
void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)")
void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
- String sessionId, in MediaRoute2Info route);
+ String sessionId, in MediaRoute2Info route,
+ in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName);
void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, int volume);
void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 8ad35876989d..0eabe66e9a69 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -19,6 +19,7 @@ package android.media;
import static android.media.MediaRouter2Utils.toUniqueId;
import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
import android.annotation.FlaggedApi;
@@ -479,6 +480,37 @@ public final class MediaRoute2Info implements Parcelable {
public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
"android.media.route.feature.REMOTE_GROUP_PLAYBACK";
+ /** Indicates the route is always suitable for media playback. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0;
+
+ /**
+ * Indicates that the route is suitable for media playback only after explicit user selection.
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1;
+
+ /** Indicates that the route is never suitable for media playback. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2;
+
+ /**
+ * Route suitability status.
+ *
+ * <p>Signals whether the route is suitable to play media.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER,
+ SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER,
+ SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public @interface SuitabilityStatus {}
+
private final String mId;
private final CharSequence mName;
private final List<String> mFeatures;
@@ -500,6 +532,7 @@ public final class MediaRoute2Info implements Parcelable {
private final String mProviderId;
private final boolean mIsVisibilityRestricted;
private final Set<String> mAllowedPackages;
+ @SuitabilityStatus private final int mSuitabilityStatus;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
@@ -521,6 +554,7 @@ public final class MediaRoute2Info implements Parcelable {
mProviderId = builder.mProviderId;
mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
mAllowedPackages = builder.mAllowedPackages;
+ mSuitabilityStatus = builder.mSuitabilityStatus;
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -544,6 +578,7 @@ public final class MediaRoute2Info implements Parcelable {
mProviderId = in.readString();
mIsVisibilityRestricted = in.readBoolean();
mAllowedPackages = Set.of(in.createString8Array());
+ mSuitabilityStatus = in.readInt();
}
/**
@@ -778,6 +813,13 @@ public final class MediaRoute2Info implements Parcelable {
|| mAllowedPackages.contains(packageName);
}
+ /** Returns the route suitability status. */
+ @SuitabilityStatus
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public int getSuitabilityStatus() {
+ return mSuitabilityStatus;
+ }
+
/**
* Dumps the current state of the object to the given {@code pw} as a human-readable string.
*
@@ -809,6 +851,7 @@ public final class MediaRoute2Info implements Parcelable {
pw.println(indent + "mProviderId=" + mProviderId);
pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
+ pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
}
private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -861,39 +904,74 @@ public final class MediaRoute2Info implements Parcelable {
&& Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
&& Objects.equals(mProviderId, other.mProviderId)
&& (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
- && Objects.equals(mAllowedPackages, other.mAllowedPackages);
+ && Objects.equals(mAllowedPackages, other.mAllowedPackages)
+ && mSuitabilityStatus == other.mSuitabilityStatus;
}
@Override
public int hashCode() {
// Note: mExtras is not included.
- return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
- mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
- mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
- mAllowedPackages);
+ return Objects.hash(
+ mId,
+ mName,
+ mFeatures,
+ mType,
+ mIsSystem,
+ mIconUri,
+ mDescription,
+ mConnectionState,
+ mClientPackageName,
+ mPackageName,
+ mVolumeHandling,
+ mVolumeMax,
+ mVolume,
+ mAddress,
+ mDeduplicationIds,
+ mProviderId,
+ mIsVisibilityRestricted,
+ mAllowedPackages,
+ mSuitabilityStatus);
}
@Override
public String toString() {
// Note: mExtras is not printed here.
- StringBuilder result = new StringBuilder()
- .append("MediaRoute2Info{ ")
- .append("id=").append(getId())
- .append(", name=").append(getName())
- .append(", features=").append(getFeatures())
- .append(", iconUri=").append(getIconUri())
- .append(", description=").append(getDescription())
- .append(", connectionState=").append(getConnectionState())
- .append(", clientPackageName=").append(getClientPackageName())
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volume=").append(getVolume())
- .append(", address=").append(getAddress())
- .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
- .append(", providerId=").append(getProviderId())
- .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
- .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
- .append(" }");
+ StringBuilder result =
+ new StringBuilder()
+ .append("MediaRoute2Info{ ")
+ .append("id=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", features=")
+ .append(getFeatures())
+ .append(", iconUri=")
+ .append(getIconUri())
+ .append(", description=")
+ .append(getDescription())
+ .append(", connectionState=")
+ .append(getConnectionState())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", volumeHandling=")
+ .append(getVolumeHandling())
+ .append(", volumeMax=")
+ .append(getVolumeMax())
+ .append(", volume=")
+ .append(getVolume())
+ .append(", address=")
+ .append(getAddress())
+ .append(", deduplicationIds=")
+ .append(String.join(",", getDeduplicationIds()))
+ .append(", providerId=")
+ .append(getProviderId())
+ .append(", isVisibilityRestricted=")
+ .append(mIsVisibilityRestricted)
+ .append(", allowedPackages=")
+ .append(String.join(",", mAllowedPackages))
+ .append(", suitabilityStatus=")
+ .append(mSuitabilityStatus)
+ .append(" }");
return result.toString();
}
@@ -923,6 +1001,7 @@ public final class MediaRoute2Info implements Parcelable {
dest.writeString(mProviderId);
dest.writeBoolean(mIsVisibilityRestricted);
dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+ dest.writeInt(mSuitabilityStatus);
}
private static String getDeviceTypeString(@Type int deviceType) {
@@ -1005,6 +1084,7 @@ public final class MediaRoute2Info implements Parcelable {
private String mProviderId;
private boolean mIsVisibilityRestricted;
private Set<String> mAllowedPackages;
+ @SuitabilityStatus private int mSuitabilityStatus;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
@@ -1028,6 +1108,7 @@ public final class MediaRoute2Info implements Parcelable {
mFeatures = new ArrayList<>();
mDeduplicationIds = Set.of();
mAllowedPackages = Set.of();
+ mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
}
/**
@@ -1075,6 +1156,7 @@ public final class MediaRoute2Info implements Parcelable {
mProviderId = routeInfo.mProviderId;
mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
mAllowedPackages = routeInfo.mAllowedPackages;
+ mSuitabilityStatus = routeInfo.mSuitabilityStatus;
}
/**
@@ -1318,6 +1400,23 @@ public final class MediaRoute2Info implements Parcelable {
}
/**
+ * Sets route suitability status.
+ *
+ * <p>The default value is {@link
+ * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}.
+ *
+ * <p> Apps are not supposed to set {@link
+ * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system
+ * route with such status throws {@link SecurityException}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) {
+ mSuitabilityStatus = suitabilityStatus;
+ return this;
+ }
+
+ /**
* Builds the {@link MediaRoute2Info media route info}.
*
* @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ba26df922f23..5e235515c852 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,6 +17,7 @@
package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2;
@@ -699,15 +700,48 @@ public final class MediaRouter2 {
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
- mImpl.transfer(controller.getRoutingSessionInfo(), route);
+ mImpl.transfer(
+ controller.getRoutingSessionInfo(),
+ route,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
+ }
+
+ /**
+ * Transfers the media of a routing controller to the given route.
+ *
+ * <p>This will be no-op for non-system media routers.
+ *
+ * @param controller a routing controller controlling media routing.
+ * @param route the route you want to transfer the media to.
+ * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer
+ * request.
+ * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
+ * This value is used with the user handle to populate {@link
+ * RoutingController#wasTransferRequestedBySelf()}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public void transfer(
+ @NonNull RoutingController controller,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
+ mImpl.transfer(
+ controller.getRoutingSessionInfo(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
void requestCreateController(
@NonNull RoutingController controller,
@NonNull MediaRoute2Info route,
- long managerRequestId) {
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
final int requestId = mNextRequestId.getAndIncrement();
@@ -736,7 +770,9 @@ public final class MediaRouter2 {
managerRequestId,
controller.getRoutingSessionInfo(),
route,
- controllerHints);
+ controllerHints,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
Log.e(TAG, "createControllerForTransfer: "
+ "Failed to request for creating a controller.", ex);
@@ -1053,7 +1089,11 @@ public final class MediaRouter2 {
}
void onRequestCreateControllerByManagerOnHandler(
- RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) {
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Log.i(
TAG,
TextUtils.formatSimple(
@@ -1070,7 +1110,8 @@ public final class MediaRouter2 {
if (controller == null) {
return;
}
- requestCreateController(controller, route, managerRequestId);
+ requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
private List<MediaRoute2Info> getSortedRoutes(
@@ -1469,6 +1510,21 @@ public final class MediaRouter2 {
}
/**
+ * Returns whether the transfer was requested by the calling app (as determined by comparing
+ * {@link UserHandle} and package name).
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public boolean wasTransferRequestedBySelf() {
+ RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
+
+ UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+ String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+ return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+ && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+ }
+
+ /**
* Returns the current {@link RoutingSessionInfo} associated to this controller.
*/
@NonNull
@@ -1980,14 +2036,20 @@ public final class MediaRouter2 {
@Override
public void requestCreateSessionByManager(
- long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+ long managerRequestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
mHandler.sendMessage(
obtainMessage(
MediaRouter2::onRequestCreateControllerByManagerOnHandler,
MediaRouter2.this,
oldSession,
route,
- managerRequestId));
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName));
}
}
@@ -2027,7 +2089,11 @@ public final class MediaRouter2 {
void stop();
- void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route);
+ void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName);
List<RoutingController> getControllers();
@@ -2220,7 +2286,11 @@ public final class MediaRouter2 {
List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route);
+ transfer(
+ targetSession,
+ route,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
}
@Override
@@ -2243,14 +2313,24 @@ public final class MediaRouter2 {
*
* @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
* @param route The {@link MediaRoute2Info route} to transfer to.
- * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info)
+ * @param transferInitiatorUserHandle The user handle of the app that initiated the
+ * transfer.
+ * @param transferInitiatorPackageName The package name if of the app that initiated the
+ * transfer.
+ * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String)
* @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
*/
@Override
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
public void transfer(
- @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
Log.v(
TAG,
@@ -2268,9 +2348,14 @@ public final class MediaRouter2 {
}
if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- transferToRoute(sessionInfo, route);
+ transferToRoute(
+ sessionInfo,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} else {
- requestCreateSession(sessionInfo, route);
+ requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
}
@@ -2282,21 +2367,30 @@ public final class MediaRouter2 {
* RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes()
* transferable routes list}. Otherwise, the request will fail.
*
- * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request
- * an out-of-session transfer.
+ * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request an
+ * out-of-session transfer.
*
* @param session The {@link RoutingSessionInfo routing session} to transfer.
* @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link
* RoutingSessionInfo routing session's} {@link
* RoutingSessionInfo#getTransferableRoutes() transferable routes}.
*/
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
private void transferToRoute(
- @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo session,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
int requestId = createTransferRequest(session, route);
try {
mMediaRouterService.transferToRouteWithManager(
- mClient, requestId, session.getId(), route);
+ mClient,
+ requestId,
+ session.getId(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -2317,7 +2411,10 @@ public final class MediaRouter2 {
* @param route The {@link MediaRoute2Info route} to transfer to.
*/
private void requestCreateSession(
- @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
this.onTransferFailed(oldSession, route);
@@ -2328,7 +2425,12 @@ public final class MediaRouter2 {
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient, requestId, oldSession, route);
+ mClient,
+ requestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -3055,7 +3157,8 @@ public final class MediaRouter2 {
return;
}
- requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+ requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
+ android.os.Process.myUserHandle(), mContext.getPackageName());
}
@Override
@@ -3071,7 +3174,11 @@ public final class MediaRouter2 {
* #transferTo(MediaRoute2Info)}.
*/
@Override
- public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
+ public void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
// Do nothing.
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 830708cb38b2..06c0996785c2 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -18,9 +18,11 @@ package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -28,6 +30,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -467,30 +470,42 @@ public final class MediaRouter2Manager {
* <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
* session based on the provided package name.
*/
- public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void transfer(
+ @NonNull String packageName,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName, "packageName must not be null");
Objects.requireNonNull(route, "route must not be null");
List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route);
+ transfer(targetSession, route, userHandle, packageName);
}
/**
* Transfers a routing session to a media route.
+ *
* <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called
* depending on the result.
*
* @param sessionInfo the routing session info to transfer
* @param route the route transfer to
- *
+ * @param transferInitiatorUserHandle the user handle of an app initiated the transfer
+ * @param transferInitiatorPackageName the package name of an app initiated the transfer
* @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
* @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info)
*/
- public void transfer(@NonNull RoutingSessionInfo sessionInfo,
- @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void transfer(
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route);
@@ -503,9 +518,11 @@ public final class MediaRouter2Manager {
}
if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
- transferToRoute(sessionInfo, route);
+ transferToRoute(
+ sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName);
} else {
- requestCreateSession(sessionInfo, route);
+ requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
}
@@ -873,19 +890,30 @@ public final class MediaRouter2Manager {
*
* @hide
*/
- private void transferToRoute(@NonNull RoutingSessionInfo session,
- @NonNull MediaRoute2Info route) {
+ @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ private void transferToRoute(
+ @NonNull RoutingSessionInfo session,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
int requestId = createTransferRequest(session, route);
try {
mMediaRouterService.transferToRouteWithManager(
- mClient, requestId, session.getId(), route);
+ mClient,
+ requestId,
+ session.getId(),
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
- private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) {
+ private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiationPackageName) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
notifyTransferFailed(oldSession, route);
@@ -896,7 +924,8 @@ public final class MediaRouter2Manager {
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient, requestId, oldSession, route);
+ mClient, requestId, oldSession, route, transferInitiatorUserHandle,
+ transferInitiationPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index a77c9432f197..d28c26df6749 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -16,18 +16,25 @@
package android.media;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -55,6 +62,33 @@ public final class RoutingSessionInfo implements Parcelable {
private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
private static final String KEY_VOLUME_HANDLING = "volumeHandling";
+ /**
+ * Indicates that the transfer happened by the default logic without explicit system's or user's
+ * request.
+ *
+ * <p>For example, an automatically connected Bluetooth device will have this transfer reason.
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_FALLBACK = 0;
+
+ /** Indicates that the transfer happened from within a privileged application. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1;
+
+ /** Indicates that the transfer happened from a non-privileged app. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public static final int TRANSFER_REASON_APP = 2;
+
+ /**
+ * Indicates the transfer reason.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransferReason {}
+
@NonNull
final String mId;
@Nullable
@@ -82,6 +116,10 @@ public final class RoutingSessionInfo implements Parcelable {
final Bundle mControlHints;
final boolean mIsSystemSession;
+ @TransferReason final int mTransferReason;
+
+ @Nullable final UserHandle mTransferInitiatorUserHandle;
+ @Nullable final String mTransferInitiatorPackageName;
RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
@@ -116,6 +154,9 @@ public final class RoutingSessionInfo implements Parcelable {
volumeAdjustmentForRemoteGroupSessions);
mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
+ mTransferReason = builder.mTransferReason;
+ mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle;
+ mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName;
}
RoutingSessionInfo(@NonNull Parcel src) {
@@ -140,6 +181,9 @@ public final class RoutingSessionInfo implements Parcelable {
mControlHints = src.readBundle();
mIsSystemSession = src.readBoolean();
+ mTransferReason = src.readInt();
+ mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+ mTransferInitiatorPackageName = src.readString();
}
@Nullable
@@ -330,6 +374,27 @@ public final class RoutingSessionInfo implements Parcelable {
return mIsSystemSession;
}
+ /** Returns the transfer reason for this routing session. */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @TransferReason
+ public int getTransferReason() {
+ return mTransferReason;
+ }
+
+ /** @hide */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @Nullable
+ public UserHandle getTransferInitiatorUserHandle() {
+ return mTransferInitiatorUserHandle;
+ }
+
+ /** @hide */
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ @Nullable
+ public String getTransferInitiatorPackageName() {
+ return mTransferInitiatorPackageName;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -351,6 +416,13 @@ public final class RoutingSessionInfo implements Parcelable {
dest.writeInt(mVolume);
dest.writeBundle(mControlHints);
dest.writeBoolean(mIsSystemSession);
+ dest.writeInt(mTransferReason);
+ if (mTransferInitiatorUserHandle != null) {
+ mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
+ } else {
+ dest.writeParcelable(null, /* flags= */ 0);
+ }
+ dest.writeString(mTransferInitiatorPackageName);
}
/**
@@ -379,6 +451,9 @@ public final class RoutingSessionInfo implements Parcelable {
pw.println(indent + "mVolume=" + mVolume);
pw.println(indent + "mControlHints=" + mControlHints);
pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
+ pw.println(indent + "mTransferReason=" + mTransferReason);
+ pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle);
+ pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName);
}
@Override
@@ -406,39 +481,69 @@ public final class RoutingSessionInfo implements Parcelable {
&& Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
&& (mVolumeHandling == other.mVolumeHandling)
&& (mVolumeMax == other.mVolumeMax)
- && (mVolume == other.mVolume);
+ && (mVolume == other.mVolume)
+ && (mTransferReason == other.mTransferReason)
+ && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
+ && Objects.equals(
+ mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId,
- mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
- mVolumeMax, mVolumeHandling, mVolume);
+ return Objects.hash(
+ mId,
+ mName,
+ mOwnerPackageName,
+ mClientPackageName,
+ mProviderId,
+ mSelectedRoutes,
+ mSelectableRoutes,
+ mDeselectableRoutes,
+ mTransferableRoutes,
+ mVolumeMax,
+ mVolumeHandling,
+ mVolume,
+ mTransferReason,
+ mTransferInitiatorUserHandle,
+ mTransferInitiatorPackageName);
}
@Override
public String toString() {
- StringBuilder result = new StringBuilder()
- .append("RoutingSessionInfo{ ")
- .append("sessionId=").append(getId())
- .append(", name=").append(getName())
- .append(", clientPackageName=").append(getClientPackageName())
- .append(", selectedRoutes={")
- .append(String.join(",", getSelectedRoutes()))
- .append("}")
- .append(", selectableRoutes={")
- .append(String.join(",", getSelectableRoutes()))
- .append("}")
- .append(", deselectableRoutes={")
- .append(String.join(",", getDeselectableRoutes()))
- .append("}")
- .append(", transferableRoutes={")
- .append(String.join(",", getTransferableRoutes()))
- .append("}")
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volume=").append(getVolume())
- .append(" }");
+ StringBuilder result =
+ new StringBuilder()
+ .append("RoutingSessionInfo{ ")
+ .append("sessionId=")
+ .append(getId())
+ .append(", name=")
+ .append(getName())
+ .append(", clientPackageName=")
+ .append(getClientPackageName())
+ .append(", selectedRoutes={")
+ .append(String.join(",", getSelectedRoutes()))
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(String.join(",", getSelectableRoutes()))
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(String.join(",", getDeselectableRoutes()))
+ .append("}")
+ .append(", transferableRoutes={")
+ .append(String.join(",", getTransferableRoutes()))
+ .append("}")
+ .append(", volumeHandling=")
+ .append(getVolumeHandling())
+ .append(", volumeMax=")
+ .append(getVolumeMax())
+ .append(", volume=")
+ .append(getVolume())
+ .append(", transferReason=")
+ .append(getTransferReason())
+ .append(", transferInitiatorUserHandle=")
+ .append(getTransferInitiatorUserHandle())
+ .append(", transferInitiatorPackageName=")
+ .append(getTransferInitiatorPackageName())
+ .append(" }");
return result.toString();
}
@@ -494,6 +599,9 @@ public final class RoutingSessionInfo implements Parcelable {
@Nullable
private Bundle mControlHints;
private boolean mIsSystemSession;
+ @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK;
+ @Nullable private UserHandle mTransferInitiatorUserHandle;
+ @Nullable private String mTransferInitiatorPackageName;
/**
* Constructor for builder to create {@link RoutingSessionInfo}.
@@ -555,6 +663,9 @@ public final class RoutingSessionInfo implements Parcelable {
mControlHints = sessionInfo.mControlHints;
mIsSystemSession = sessionInfo.mIsSystemSession;
+ mTransferReason = sessionInfo.mTransferReason;
+ mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle;
+ mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName;
}
/**
@@ -784,6 +895,35 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
+ * Sets transfer reason for the current session.
+ *
+ * <p>By default the transfer reason is set to {@link
+ * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setTransferReason(@TransferReason int transferReason) {
+ mTransferReason = transferReason;
+ return this;
+ }
+
+ /**
+ * Sets the user handle and package name of the process that initiated the transfer.
+ *
+ * <p>By default the transfer initiation user handle and package name are set to {@code
+ * null}.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+ public Builder setTransferInitiator(
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
+ mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+ mTransferInitiatorPackageName = transferInitiatorPackageName;
+ return this;
+ }
+
+ /**
* Builds a routing session info.
*
* @throws IllegalArgumentException if no selected routes are added.
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e16849811b9d..b85decc74ff8 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -16,6 +16,8 @@
package android.media.audiopolicy;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioService;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
@@ -49,6 +52,7 @@ import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -612,6 +616,110 @@ public class AudioPolicy {
return mRegistrationId;
}
+ /**
+ * Sets a custom {@link FadeManagerConfiguration} to handle fade cycle of players during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * @param fmcForFocusLoss custom {@link FadeManagerConfiguration}
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ Objects.requireNonNull(fmcForFocusLoss,
+ "FadeManagerConfiguration for focus loss cannot be null");
+
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot set FadeManagerConfiguration with unregistered AudioPolicy");
+
+ try {
+ return service.setFadeManagerConfigurationForFocusLoss(fmcForFocusLoss);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for setFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Clear the current {@link FadeManagerConfiguration} set to handle fade cycles of players
+ * during {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>In the absence of custom {@link FadeManagerConfiguration}, the default configurations will
+ * be used to handle fade cycles during audio focus loss.
+ *
+ * @return {@link AudioManager#SUCCESS} if the update was successful,
+ * {@link AudioManager#ERROR} otherwise
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot clear FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.clearFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for "
+ + "clearFadeManagerConfigurationForFocusLoss:", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Get the current fade manager configuration used for fade operations during
+ * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}
+ *
+ * <p>If no custom {@link FadeManagerConfiguration} is set, the default configuration currently
+ * active will be returned.
+ *
+ * @return the active {@link FadeManagerConfiguration} used during audio focus loss
+ * @throws IllegalStateException if the audio policy is not registered
+ * @see #setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)
+ * @see #clearFadeManagerConfigurationForFocusLoss()
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ IAudioService service = getService();
+ synchronized (mLock) {
+ Preconditions.checkState(isAudioPolicyRegisteredLocked(),
+ "Cannot get FadeManagerConfiguration from unregistered AudioPolicy");
+
+ try {
+ return service.getFadeManagerConfigurationForFocusLoss();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Received remote exception for getFadeManagerConfigurationForFocusLoss:",
+ e);
+ throw e.rethrowFromSystemServer();
+
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioPolicyRegisteredLocked() {
+ return mStatus == POLICY_STATUS_REGISTERED;
+ }
+
private boolean policyReadyToUse() {
synchronized (mLock) {
if (mStatus != POLICY_STATUS_REGISTERED) {
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
deleted file mode 100644
index 100e2235a7a8..000000000000
--- a/media/java/android/media/flags/fade_manager_configuration.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.media.flags"
-
-flag {
- namespace: "media_solutions"
- name: "enable_fade_manager_configuration"
- description: "Enable Fade Manager Configuration support to determine fade properties"
- bug: "307354764"
-} \ No newline at end of file
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 07f63e5441af..7f9588644052 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -69,3 +69,17 @@ flag {
description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
bug: "314324170"
}
+
+flag {
+ name: "enable_built_in_speaker_route_suitability_statuses"
+ namespace: "media_solutions"
+ description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+ bug: "279555229"
+}
+
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6d5861..60497fe22dcf 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@ public final class PlaybackState implements Parcelable {
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 757e9f8e41b1..3fcb8713672f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int
mpuSequenceNumber, isPesPrivateData, sc,
audioDescriptor.get(), presentationsJObj.get()));
+ // Protect mFilterClient from being set to null.
+ android::Mutex::Autolock autoLock(mLock);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@ void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &eve
}
}
}
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterEventID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterEventID;
+
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(mFilterObj, nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, array.get());
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterEvent:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, array.get());
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterStatusID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterStatusID;
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(filter.get(), nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
}
void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = false;
@@ -979,6 +995,7 @@ void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filte
void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = true;
@@ -1047,11 +1064,14 @@ FilterClientCallbackImpl::FilterClientCallbackImpl() {
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
+ }
+ mFilterClient = nullptr;
}
- mFilterClient = nullptr;
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
@@ -3696,7 +3716,7 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) {
"([Landroid/media/tv/tuner/filter/FilterEvent;)V");
jclass sharedFilterClazz = env->FindClass("android/media/tv/tuner/filter/SharedFilter");
- gFields.sharedFilterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
+ gFields.sharedFilterContext = env->GetFieldID(sharedFilterClazz, "mNativeContext", "J");
gFields.sharedFilterInitID = env->GetMethodID(sharedFilterClazz, "<init>", "()V");
gFields.onSharedFilterStatusID = env->GetMethodID(sharedFilterClazz, "onFilterStatus", "(I)V");
gFields.onSharedFilterEventID =
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998dab8d6..3de3ab91326c 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@ struct FilterClientCallbackImpl : public FilterClientCallback {
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ android::Mutex mLock;
jclass mEventClass;
jclass mSectionEventClass;
jclass mMediaEventClass;
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index fb6bd489d5d0..f105ae9cc33e 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -16,7 +16,7 @@
package com.android.audiopolicytest;
-import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import static org.junit.Assert.assertThrows;
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 8ed4bf2b9cc3..c836df3b2c4d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -385,7 +385,9 @@ public class MediaRouter2ManagerTest {
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertThat(routeToSelect).isNotNull();
- mManager.transfer(mPackageName, routeToSelect);
+ mManager.transfer(
+ mPackageName, routeToSelect,
+ android.os.Process.myUserHandle());
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRemoteSessions()).hasSize(1);
}
@@ -411,7 +413,9 @@ public class MediaRouter2ManagerTest {
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
- mManager.transfer(mPackageName, routeToSelect);
+ mManager.transfer(
+ mPackageName, routeToSelect,
+ android.os.Process.myUserHandle());
assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -450,7 +454,11 @@ public class MediaRouter2ManagerTest {
.addFeature(FEATURE_REMOTE_PLAYBACK)
.build();
- mManager.transfer(mManager.getSystemRoutingSession(null), unknownRoute);
+ mManager.transfer(
+ mManager.getSystemRoutingSession(null),
+ unknownRoute,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
@@ -484,7 +492,11 @@ public class MediaRouter2ManagerTest {
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
assertThat(mRouter2.getControllers()).hasSize(1);
- mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect);
+ mManager.transfer(
+ mManager.getRoutingSessions(mPackageName).get(0),
+ routeToSelect,
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(2);
@@ -516,7 +528,11 @@ public class MediaRouter2ManagerTest {
}
});
awaitOnRouteChangedManager(
- () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
+ () ->
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID1),
+ android.os.Process.myUserHandle()),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -527,7 +543,11 @@ public class MediaRouter2ManagerTest {
RoutingSessionInfo sessionInfo = sessions.get(1);
awaitOnRouteChangedManager(
- () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+ () ->
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID5_TO_TRANSFER_TO),
+ android.os.Process.myUserHandle()),
ROUTE_ID5_TO_TRANSFER_TO,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -585,9 +605,11 @@ public class MediaRouter2ManagerTest {
assertThat(route1).isNotNull();
assertThat(route2).isNotNull();
- mManager.transfer(mPackageName, route1);
+ mManager.transfer(
+ mPackageName, route1, android.os.Process.myUserHandle());
assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
- mManager.transfer(mPackageName, route2);
+ mManager.transfer(
+ mPackageName, route2, android.os.Process.myUserHandle());
assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
// onTransferFailed/onSessionReleased should not be called.
@@ -634,7 +656,11 @@ public class MediaRouter2ManagerTest {
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1);
- mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED));
+ mManager.transfer(
+ targetSession,
+ routes.get(ROUTE_ID6_TO_BE_IGNORED),
+ android.os.Process.myUserHandle(),
+ mContext.getPackageName());
assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
assertThat(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS,
@@ -705,7 +731,10 @@ public class MediaRouter2ManagerTest {
}
});
- mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID1),
+ android.os.Process.myUserHandle());
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -860,7 +889,8 @@ public class MediaRouter2ManagerTest {
});
mRouter2.setOnGetControllerHintsListener(listener);
- mManager.transfer(mPackageName, route);
+ mManager.transfer(
+ mPackageName, route, android.os.Process.myUserHandle());
assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -905,7 +935,10 @@ public class MediaRouter2ManagerTest {
}
});
- mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ mManager.transfer(
+ mPackageName,
+ routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT),
+ android.os.Process.myUserHandle());
assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c4c81284780e..abe4a3d18b38 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,10 +18,12 @@
#include <aidl/android/hardware/power/SessionHint.h>
#include <aidl/android/hardware/power/SessionMode.h>
+#include <android-base/stringprintf.h>
#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
+#include <android/trace.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
@@ -30,6 +32,7 @@
#include <utils/SystemClock.h>
#include <chrono>
+#include <set>
#include <utility>
#include <vector>
@@ -40,6 +43,7 @@ using namespace std::chrono_literals;
using AidlSessionHint = aidl::android::hardware::power::SessionHint;
using AidlSessionMode = aidl::android::hardware::power::SessionMode;
+using android::base::StringPrintf;
struct APerformanceHintSession;
@@ -98,10 +102,21 @@ private:
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
std::vector<WorkDuration> mActualWorkDurations;
+ std::string mSessionName;
+ static int32_t sIDCounter;
+ // The most recent set of thread IDs
+ std::vector<int32_t> mLastThreadIDs;
+ // Tracing helpers
+ void traceThreads(std::vector<int32_t>& tids);
+ void tracePowerEfficient(bool powerEfficient);
+ void traceActualDuration(int64_t actualDuration);
+ void traceBatchSize(size_t batchSize);
+ void traceTargetDuration(int64_t targetDuration);
};
static IHintManager* gIHintManagerForTesting = nullptr;
static APerformanceHintManager* gHintManagerForTesting = nullptr;
+int32_t APerformanceHintSession::sIDCounter = 0;
// ===================================== APerformanceHintManager implementation
APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
@@ -150,8 +165,12 @@ APerformanceHintSession* APerformanceHintManager::createSession(
if (!ret.isOk() || !session) {
return nullptr;
}
- return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
- initialTargetWorkDurationNanos);
+ auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
+ initialTargetWorkDurationNanos);
+ out->traceThreads(tids);
+ out->traceTargetDuration(initialTargetWorkDurationNanos);
+ out->tracePowerEfficient(false);
+ return out;
}
int64_t APerformanceHintManager::getPreferredRateNanos() const {
@@ -174,6 +193,7 @@ APerformanceHintSession::APerformanceHintSession(sp<IHintManager> hintManager,
ndk::enum_range<AidlSessionHint>().end()};
mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+ mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
}
APerformanceHintSession::~APerformanceHintSession() {
@@ -200,6 +220,8 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano
* as they are most likely obsolete.
*/
mActualWorkDurations.clear();
+ traceBatchSize(0);
+ traceTargetDuration(targetDurationNanos);
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
return 0;
@@ -254,6 +276,9 @@ int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
}
return EPIPE;
}
+
+ traceThreads(tids);
+
return 0;
}
@@ -289,6 +314,7 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
ret.exceptionMessage().c_str());
return EPIPE;
}
+ tracePowerEfficient(enabled);
return OK;
}
@@ -318,6 +344,7 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
int64_t now = uptimeNanos();
workDuration->timestampNanos = now;
+ traceActualDuration(workDuration->actualTotalDurationNanos);
mActualWorkDurations.push_back(std::move(*workDuration));
if (actualTotalDurationNanos >= mTargetDurationNanos) {
@@ -335,6 +362,7 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
*/
if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+ traceBatchSize(mActualWorkDurations.size());
return 0;
}
mLastTargetMetTimestamp = now;
@@ -346,12 +374,54 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
ret.exceptionMessage().c_str());
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
+ traceBatchSize(mActualWorkDurations.size());
return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
}
mActualWorkDurations.clear();
+ traceBatchSize(0);
return 0;
}
+// ===================================== Tracing helpers
+
+void APerformanceHintSession::traceThreads(std::vector<int32_t>& tids) {
+ std::set<int32_t> tidSet{tids.begin(), tids.end()};
+
+ // Disable old TID tracing
+ for (int32_t tid : mLastThreadIDs) {
+ if (!tidSet.count(tid)) {
+ std::string traceName =
+ android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+ ATrace_setCounter(traceName.c_str(), 0);
+ }
+ }
+
+ // Add new TID tracing
+ for (int32_t tid : tids) {
+ std::string traceName =
+ android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+ ATrace_setCounter(traceName.c_str(), 1);
+ }
+
+ mLastThreadIDs = std::move(tids);
+}
+
+void APerformanceHintSession::tracePowerEfficient(bool powerEfficient) {
+ ATrace_setCounter((mSessionName + " power efficiency mode").c_str(), powerEfficient);
+}
+
+void APerformanceHintSession::traceActualDuration(int64_t actualDuration) {
+ ATrace_setCounter((mSessionName + " actual duration").c_str(), actualDuration);
+}
+
+void APerformanceHintSession::traceBatchSize(size_t batchSize) {
+ std::string traceName = StringPrintf("%s batch size", mSessionName.c_str());
+ ATrace_setCounter((mSessionName + " batch size").c_str(), batchSize);
+}
+
+void APerformanceHintSession::traceTargetDuration(int64_t targetDuration) {
+ ATrace_setCounter((mSessionName + " target duration").c_str(), targetDuration);
+}
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb43a11d..1ac5db673838 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- style="@style/ScrollViewStyle">
+ style="@style/ScrollViewStyle"
+ android:importantForAccessibility="no">
<LinearLayout
- android:id="@+id/data_transfer_confirmation"
- style="@style/ContainerLayout">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:importantForAccessibility="no">
- <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
+ <LinearLayout
+ android:id="@+id/data_transfer_confirmation"
+ style="@style/ContainerLayout">
- <ImageView
- android:id="@+id/header_icon"
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="18dp"
- android:src="@drawable/ic_warning"
- android:contentDescription="@null" />
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
- <LinearLayout style="@style/Description">
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center"
+ android:layout_marginTop="18dp"
+ android:src="@drawable/ic_warning"
+ android:contentDescription="@null" />
- <TextView
- android:id="@+id/title"
- style="@style/DescriptionTitle" />
+ <LinearLayout style="@style/Description">
- <TextView
- android:id="@+id/summary"
- style="@style/DescriptionSummary" />
+ <TextView
+ android:id="@+id/title"
+ style="@style/DescriptionTitle" />
- </LinearLayout>
+ <TextView
+ android:id="@+id/summary"
+ style="@style/DescriptionSummary" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="18dp">
-
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
-
- <Button
- android:id="@+id/btn_positive"
- style="@style/PositiveButton"
- android:text="@string/consent_yes" />
-
- <Button
- android:id="@+id/btn_negative"
- style="@style/NegativeButton"
- android:text="@string/consent_no" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="18dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_positive"
+ style="@style/PositiveButton"
+ android:text="@string/consent_yes" />
+
+ <Button
+ android:id="@+id/btn_negative"
+ style="@style/NegativeButton"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11bfaf1..6a4bb216b495 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@ public class LocalTransport extends BackupTransport {
@UsesReflection({
// As the runtime class name is used to generate the returned name, and the returned
// name may be used used with reflection, generate the necessary keep rules.
- @KeepTarget(classConstant = LocalTransport.class),
- @KeepTarget(extendsClassConstant = LocalTransport.class)
+ @KeepTarget(instanceOfClassConstant = LocalTransport.class)
})
@Override
public String name() {
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index df72a921544d..044ba872f3e5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -15,9 +15,12 @@ android_library {
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibTile"
+ "androidx.annotation_annotation",
+ "SettingsLibTile",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2501869341ad..ffe3d1d633d7 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -60,6 +60,9 @@ android_library {
"src/**/*.java",
"src/**/*.kt",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
index 986baf70fa44..bd0dbdc6884d 100644
--- a/packages/SettingsLib/EmergencyNumber/Android.bp
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -17,4 +17,7 @@ android_library {
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce9d4d9..d9f74dadf281 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,4 +28,7 @@ android_library {
"com.android.extservices",
"com.android.healthfitness",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 028489d22bdb..3b04bd99e0f4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -30,4 +30,7 @@ android_library {
"//apex_available:platform",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 2d93e4e3a925..22e4e94b80b1 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -14,9 +14,12 @@ android_library {
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c07a802f3ed5..c385d385dcc9 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -15,4 +15,7 @@ android_library {
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 66bd6f502274..d5cf1a35b4df 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -26,6 +26,7 @@ import androidx.compose.material.icons.outlined.Error
import androidx.compose.material.icons.outlined.PowerOff
import androidx.compose.material.icons.outlined.Shield
import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -78,24 +79,19 @@ object CardPageProvider : SettingsPageProvider {
imageVector = Icons.Outlined.WarningAmber,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Action", isMain = true) {},
- )
+ ),
+ tintColor = MaterialTheme.colorScheme.error,
+ containerColor = MaterialTheme.colorScheme.errorContainer,
)
)
}
@Composable
private fun SettingsCardWithoutIcon() {
- var isVisible by rememberSaveable { mutableStateOf(true) }
SettingsCard(
CardModel(
title = stringResource(R.string.sample_title),
text = stringResource(R.string.sample_text),
- isVisible = { isVisible },
- onDismiss = { isVisible = false },
- buttons = listOf(
- CardButton(text = "Action") {},
- ),
)
)
}
@@ -104,6 +100,7 @@ object CardPageProvider : SettingsPageProvider {
fun SampleSettingsCollapsibleCard() {
val context = LocalContext.current
var isVisible0 by rememberSaveable { mutableStateOf(true) }
+ var isVisible1 by rememberSaveable { mutableStateOf(true) }
val cards = remember {
mutableStateListOf(
CardModel(
@@ -114,16 +111,17 @@ object CardPageProvider : SettingsPageProvider {
onDismiss = { isVisible0 = false },
buttons = listOf(
CardButton(text = "Action") {},
- )
+ ),
),
CardModel(
title = context.getString(R.string.sample_title),
text = context.getString(R.string.sample_text),
imageVector = Icons.Outlined.Shield,
+ isVisible = { isVisible1 },
+ onDismiss = { isVisible1 = false },
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Main action", isMain = true) {},
- )
+ ),
)
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 993cb4ac85e5..c143390f269c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,7 +37,6 @@ object SettingsDimension {
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
- val iconSmall = 16.dp
val iconLarge = 48.dp
/** The size when app icon is displayed in list. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b18a1bc01388..b2a8b87a4495 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -16,11 +16,11 @@
package com.android.settingslib.spa.widget.card
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
data class CardButton(
val text: String,
- val isMain: Boolean = false,
val onClick: () -> Unit,
)
@@ -38,4 +38,10 @@ data class CardModel(
val onDismiss: (() -> Unit)? = null,
val buttons: List<CardButton> = emptyList(),
+
+ /** If specified, this color will be used to tint the icon and the buttons. */
+ val tintColor: Color = Color.Unspecified,
+
+ /** If specified, this color will be used to tint the icon and the buttons. */
+ val containerColor: Color = Color.Unspecified,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 7eec8888f025..c7845fa724d4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -23,26 +23,26 @@ import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.WarningAmber
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -72,11 +72,14 @@ fun SettingsCard(content: @Composable ColumnScope.() -> Unit) {
}
@Composable
-fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+fun SettingsCardContent(
+ containerColor: Color = Color.Unspecified,
+ content: @Composable ColumnScope.() -> Unit,
+) {
Card(
shape = CornerExtraSmall,
colors = CardDefaults.cardColors(
- containerColor = SettingsTheme.colorScheme.surface,
+ containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface },
),
modifier = Modifier
.fillMaxWidth()
@@ -95,37 +98,43 @@ fun SettingsCard(model: CardModel) {
@Composable
internal fun SettingsCardImpl(model: CardModel) {
AnimatedVisibility(visible = model.isVisible()) {
- SettingsCardContent {
+ SettingsCardContent(containerColor = model.containerColor) {
Column(
- modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+ modifier = Modifier.padding(
+ horizontal = SettingsDimension.dialogItemPaddingHorizontal,
+ vertical = SettingsDimension.itemPaddingAround,
+ ),
verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
) {
- CardHeader(model.imageVector, model.onDismiss)
+ CardHeader(model.imageVector, model.tintColor, model.onDismiss)
SettingsTitle(model.title)
SettingsBody(model.text)
- Buttons(model.buttons)
+ Buttons(model.buttons, model.tintColor)
}
}
}
}
@Composable
-fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) {
+fun CardHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) {
+ if (imageVector != null || onDismiss != null) {
+ Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical))
+ }
Row(Modifier.fillMaxWidth()) {
- CardIcon(imageVector)
+ CardIcon(imageVector, iconColor)
Spacer(modifier = Modifier.weight(1f))
DismissButton(onDismiss)
}
}
@Composable
-private fun CardIcon(imageVector: ImageVector?) {
+private fun CardIcon(imageVector: ImageVector?, color: Color) {
if (imageVector != null) {
Icon(
imageVector = imageVector,
contentDescription = null,
modifier = Modifier.size(SettingsDimension.itemIconSize),
- tint = MaterialTheme.colorScheme.primary,
+ tint = color.takeOrElse { MaterialTheme.colorScheme.primary },
)
}
}
@@ -146,52 +155,35 @@ private fun DismissButton(onDismiss: (() -> Unit)?) {
contentDescription = stringResource(
androidx.compose.material3.R.string.m3c_snackbar_dismiss
),
- modifier = Modifier.size(SettingsDimension.iconSmall),
+ modifier = Modifier.padding(SettingsDimension.paddingSmall),
)
}
}
}
@Composable
-private fun Buttons(buttons: List<CardButton>) {
+private fun Buttons(buttons: List<CardButton>, color: Color) {
if (buttons.isNotEmpty()) {
Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = SettingsDimension.itemPaddingAround),
+ modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
space = SettingsDimension.itemPaddingEnd,
alignment = Alignment.End,
),
) {
for (button in buttons) {
- Button(button)
+ Button(button, color)
}
}
+ } else {
+ Spacer(Modifier.height(SettingsDimension.itemPaddingAround))
}
}
@Composable
-private fun Button(button: CardButton) {
- if (button.isMain) {
- Button(
- onClick = button.onClick,
- colors = ButtonDefaults.buttonColors(
- containerColor = SettingsTheme.colorScheme.primaryContainer,
- ),
- ) {
- Text(
- text = button.text,
- color = SettingsTheme.colorScheme.onPrimaryContainer,
- )
- }
- } else {
- OutlinedButton(onClick = button.onClick) {
- Text(
- text = button.text,
- color = MaterialTheme.colorScheme.onSurface,
- )
- }
+private fun Button(button: CardButton, color: Color) {
+ TextButton(onClick = button.onClick) {
+ Text(text = button.text, color = color)
}
}
@@ -206,7 +198,6 @@ private fun SettingsCardPreview() {
imageVector = Icons.Outlined.WarningAmber,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Action", isMain = true) {},
)
)
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 6e36490beac7..c34df653f03c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -141,7 +141,6 @@ private fun SettingsCollapsibleCardPreview() {
imageVector = Icons.Outlined.Shield,
buttons = listOf(
CardButton(text = "Action") {},
- CardButton(text = "Main action", isMain = true) {},
)
)
)
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index eb9e329b6a59..19c59dd221c2 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -14,8 +14,11 @@ android_library {
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index c7ad24c6148c..d5a56c86431f 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -31,4 +31,7 @@ android_library {
"com.android.healthfitness",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 202e7fe219de..3b14712cc87e 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,6 +12,9 @@ java_library {
visibility: ["//visibility:private"],
srcs: ["interface-src/**/*.java"],
host_supported: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -24,6 +27,9 @@ android_library {
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_plugin {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index c51a9a07332f..8e5396fef744 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -23,8 +23,6 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF
import static com.android.settingslib.Utils.getColorAttrDefaultColor;
import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -52,6 +50,8 @@ import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 3b8f66577f6e..70ece0fa8076 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -21,7 +21,6 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_B
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -35,6 +34,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 107d5f8a8ae9..c2be571b444a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -3,7 +3,6 @@ package com.android.settingslib;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
import android.annotation.ColorInt;
-import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
@@ -23,10 +22,10 @@ import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
-import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.NetworkCapabilities;
@@ -47,6 +46,7 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index dae48dbac0a4..b0db16fa2e30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -17,7 +17,6 @@
package com.android.settingslib.applications;
import android.app.AppGlobals;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -28,6 +27,8 @@ import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.util.IconDrawableFactory;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.widget.CandidateInfo;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index fa056e2b77bd..416b36981a4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -26,9 +26,9 @@ import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 19005755ef58..cf4d6be9a042 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -21,8 +21,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -35,6 +33,9 @@ import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import java.lang.annotation.Retention;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
index c9512cd01aa3..8eaea0e3561b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -151,7 +151,9 @@ public class HearingAidAudioRoutingHelper {
private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
boolean status = true;
for (AudioProductStrategy strategy : strategies) {
- status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+ if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) {
+ status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+ }
}
return status;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 51164e836590..57012aabb123 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,7 +21,6 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@ import android.content.Context;
import android.os.Build;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 49ac0f864ed7..de21c541c7e4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -19,7 +19,6 @@ package com.android.settingslib.bluetooth;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -44,6 +43,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34008ac56042..34c60a1b9f34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -20,7 +20,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -33,6 +32,7 @@ import android.content.Context;
import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 3774b88db93d..ab7a3db4b3bb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -21,7 +21,6 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
-import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@ import android.content.Context;
import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index be420ac8bd24..43c6c0dbd1e5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.connectivity;
-import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,6 +30,7 @@ import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 067afa4ff726..f84715487788 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -14,7 +14,6 @@
package com.android.settingslib.core.instrumentation;
-import android.annotation.Nullable;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
@@ -24,6 +23,7 @@ import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.Map;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index 2bd0b27063ea..e974e7036945 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -22,13 +22,13 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index bbb1ec6a5623..0029e20ba626 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -16,7 +16,6 @@
package com.android.settingslib.datetime;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.icu.text.TimeZoneFormat;
@@ -29,6 +28,7 @@ import android.text.style.TtsSpan;
import android.util.Log;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.core.text.TextDirectionHeuristicsCompat;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 11fae24aa677..f07daa3add8b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -22,7 +22,6 @@ import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_USE
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
-import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -45,6 +44,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
index 0b3a519cd919..f32902329818 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
@@ -16,11 +16,11 @@
package com.android.settingslib.enterprise;
-import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.DialogInterface;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
index 714accc09763..7e3395bd14f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
@@ -16,7 +16,6 @@
package com.android.settingslib.enterprise;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -24,6 +23,7 @@ import android.net.Uri;
import android.provider.Settings;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 17c2b02ed576..71d58098cf75 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
package com.android.settingslib.graph;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -35,6 +34,8 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import com.android.settingslib.Utils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index f01eb2a5606e..65d53f30bd2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -16,7 +16,7 @@
package com.android.settingslib.graph;
-import android.annotation.NonNull;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PorterDuff;
@@ -25,6 +25,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.view.Gravity;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 4d0804e1bbb7..9a19f9368449 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -16,8 +16,6 @@ package com.android.settingslib.graph;
import android.animation.ArgbEvaluator;
import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -36,6 +34,9 @@ import android.telephony.CellSignalStrength;
import android.util.LayoutDirection;
import android.util.PathParser;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import com.android.settingslib.Utils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index 1712a6b67e2f..318e3dd51f26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -16,8 +16,6 @@
package com.android.settingslib.inputmethod;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
@@ -30,6 +28,8 @@ import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 78ec58b89800..a23164351cb7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -16,8 +16,6 @@
package com.android.settingslib.inputmethod;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,6 +30,8 @@ import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 4384400eaa88..5c0c97974891 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -17,7 +17,6 @@
package com.android.settingslib.inputmethod;
import android.annotation.AnyThread;
-import android.annotation.NonNull;
import android.annotation.UiThread;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +25,8 @@ import android.util.SparseArray;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 52b51d7e42e9..ba40a5030141 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -43,8 +43,6 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
@@ -59,6 +57,8 @@ import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 0be2e0efaea2..97bbf12fd055 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.media;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -27,6 +25,9 @@ import android.media.RoutingSessionInfo;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -80,14 +81,17 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
@Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
- mRouterManager.transfer(mPackageName, route);
+ // TODO: b/279555229 - provide real user handle of a caller.
+ mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
}
@Override
protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
if (info != null) {
- mRouterManager.transfer(info, device.mRouteInfo);
+ // TODO: b/279555229 - provide real user handle and package name of a caller.
+ mRouterManager.transfer(
+ info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
return true;
}
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 80eeab56cc45..0676ce5a65f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -29,7 +29,6 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.Manifest;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -40,6 +39,7 @@ import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index 5e9ac5a59091..bcfdebe74976 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -16,7 +16,6 @@
package com.android.settingslib.net;
-import android.annotation.NonNull;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -27,6 +26,7 @@ import android.text.format.DateUtils;
import android.util.Pair;
import android.util.Range;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.loader.content.AsyncTaskLoader;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 7fb959ae4d76..dfa5ed12b7dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -16,7 +16,6 @@
package com.android.settingslib.notification;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
@@ -43,6 +42,8 @@ import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index fbf8a2f2cf4f..23b2cc2df794 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -16,8 +16,6 @@
package com.android.settingslib.volume;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +40,9 @@ import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 303ee3c9fca2..cf452314163f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -21,7 +21,6 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_
import android.annotation.IntDef;
import android.annotation.MainThread;
-import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -58,6 +57,7 @@ import android.util.Pair;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 98272cc1dabd..e5085260b56f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -15,7 +15,6 @@
*/
package com.android.settingslib.wifi;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -32,6 +31,7 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
index 7ffae4094add..c0ca12529897 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
@@ -18,12 +18,12 @@ package com.android.settingslib.wifi;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
-import android.annotation.NonNull;
import android.content.Context;
import android.os.Bundle;
import android.os.UserManager;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.HashMap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
index a0c2698da4b5..14967ecb6ee0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
@@ -22,7 +22,6 @@ import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import android.annotation.AnyThread;
-import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -34,6 +33,7 @@ import android.os.Process;
import android.util.Log;
import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
index 8b5ea30e17fe..c83524462b15 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -18,8 +18,10 @@ package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
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 static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -79,6 +82,8 @@ public class HearingAidAudioRoutingHelperTest {
when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
new AudioDeviceInfo[]{mAudioDeviceInfo});
+ doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
+ any(AudioProductStrategy.class));
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
@@ -92,7 +97,10 @@ public class HearingAidAudioRoutingHelperTest {
}
@Test
- public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+ public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
+ when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
+ mHearingDeviceAttribute);
+
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
HearingAidAudioRoutingConstants.RoutingValue.AUTO);
@@ -101,6 +109,17 @@ public class HearingAidAudioRoutingHelperTest {
}
@Test
+ public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() {
+ when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
+
+ mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+ mHearingDeviceAttribute,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+ verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
+ }
+
+ @Test
public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
mHearingDeviceAttribute,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 5aee8cdb8b9e..194a0e29de34 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -27,9 +27,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.preference.PreferenceGroupAdapter;
import androidx.preference.SwitchPreference;
import androidx.recyclerview.widget.RecyclerView;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
index 1d5f1b20e31e..819d4b321b8c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
@@ -16,7 +16,7 @@
package com.android.settingslib.enterprise;
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index c79440e58e17..77c46f7105a7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.app.usage.NetworkStats;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
@@ -37,6 +36,8 @@ import android.net.NetworkTemplate;
import android.text.format.DateUtils;
import android.util.Range;
+import androidx.annotation.NonNull;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
index fae3aeafd5fb..844880466ea1 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -16,12 +16,13 @@
package com.android.settingslib.testutils.shadow;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.AttributionSource;
import android.content.Context;
import android.content.PermissionChecker;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index dac8142732e5..fde378fc3e5e 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -15,12 +15,13 @@
*/
package com.android.settingslib.testutils.shadow;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index adebdcdcf26a..d5814e3a9b79 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -59,10 +59,10 @@ android_test {
// Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
// because this test is not an instrumentation test. (because the target runs in the system process.)
"SettingsProviderLib",
-
"androidx.test.rules",
"flag-junit",
"junit",
+ "libaconfig_java_proto_lite",
"mockito-target-minus-junit4",
"platform-test-annotations",
"truth",
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d9fe7335dbcb..3027c5f91db5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -209,6 +209,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 8f459c647316..73c2e22240d3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -18,6 +18,9 @@ package com.android.providers.settings;
import static android.os.Process.FIRST_APPLICATION_UID;
+import android.aconfig.Aconfig.flag_state;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -147,6 +150,17 @@ final class SettingsState {
*/
private static final String CONFIG_STAGED_PREFIX = "staged/";
+ private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
+
+ /**
+ * This tag is applied to all aconfig default value-loaded flags.
+ */
+ private static final String BOOT_LOADED_DEFAULT_TAG = "BOOT_LOADED_DEFAULT";
+
// This was used in version 120 and before.
private static final String NULL_VALUE_OLD_STYLE = "null";
@@ -315,6 +329,59 @@ final class SettingsState {
synchronized (mLock) {
readStateSyncLocked();
+
+ if (Flags.loadAconfigDefaults()) {
+ // Only load aconfig defaults if this is the first boot, the XML
+ // file doesn't exist yet, or this device is on its first boot after
+ // an OTA.
+ boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
+ && (!file.exists()
+ || mContext.getPackageManager().isDeviceUpgrading());
+ if (shouldLoadAconfigValues) {
+ loadAconfigDefaultValuesLocked();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void loadAconfigDefaultValuesLocked() {
+ for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ try (FileInputStream inputStream = new FileInputStream(fileName)) {
+ byte[] contents = inputStream.readAllBytes();
+ loadAconfigDefaultValues(contents);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to read protobuf", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ public void loadAconfigDefaultValues(byte[] fileContents) {
+ try {
+ parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+
+ if (parsedFlags == null) {
+ Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
+ return;
+ }
+
+ for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
+ String flagName = flag.getNamespace() + "/"
+ + flag.getPackage() + "." + flag.getName();
+ String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
+
+ Setting existingSetting = getSettingLocked(flagName);
+ boolean isDefaultLoaded = existingSetting.getTag() != null
+ && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
+ if (existingSetting.getValue() == null || isDefaultLoaded) {
+ insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
+ false, flag.getPackage());
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to parse protobuf", e);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 27ce0d4c7252..ecac5ee18582 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -6,3 +6,11 @@ flag {
description: "When enabled, allows setting and displaying local overrides via adb."
bug: "298392357"
}
+
+flag {
+ name: "load_aconfig_defaults"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values into DeviceConfig on boot."
+ bug: "311155098"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85e87691ac85..6ad10cc8cc5b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@ public class SettingsBackupTest {
Settings.Global.DSRM_DURATION_MILLIS,
Settings.Global.DSRM_ENABLED_ACTIONS,
Settings.Global.MODE_RINGER,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
Settings.Global.MULTI_SIM_SMS_PROMPT,
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 02a7bc1646ba..24625eaa5e13 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,9 @@
*/
package com.android.providers.settings;
+import android.aconfig.Aconfig;
+import android.aconfig.Aconfig.parsed_flag;
+import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
import android.test.AndroidTestCase;
import android.util.Xml;
@@ -84,6 +87,86 @@ public class SettingsStateTest extends AndroidTestCase {
super.tearDown();
}
+ public void testLoadValidAconfigProto() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag1")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag2")
+ .setNamespace("test_namespace")
+ .setDescription("another test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.ENABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals("false",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ assertEquals("true",
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag2").getValue());
+ }
+ }
+
+ public void testSkipLoadingAconfigFlagWithMissingFields() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ settingsState.loadAconfigDefaultValues(flags.toByteArray());
+ settingsState.persistSettingsLocked();
+ }
+ settingsState.waitForHandler();
+
+ synchronized (lock) {
+ assertEquals(null,
+ settingsState.getSettingLocked(
+ "test_namespace/com.android.flags.flag1").getValue());
+ }
+ }
+
+ public void testInvalidAconfigProtoDoesNotCrash() {
+ SettingsState settingsState = getSettingStateObject();
+ settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+ }
+
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1a35f04b393f..a03fa9b39bfc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -256,6 +256,9 @@
<!-- launcher apps -->
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />
+ <!-- Permission to start Launcher's widget picker activity. -->
+ <uses-permission android:name="android.permission.START_WIDGET_PICKER_ACTIVITY" />
+
<uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />
<!-- accessibility -->
@@ -974,15 +977,6 @@
android:excludeFromRecents="true"
android:visibleToInstantApps="true"/>
- <activity android:name="com.android.systemui.communal.widgets.WidgetPickerActivity"
- android:theme="@style/Theme.EditWidgetsActivity"
- android:excludeFromRecents="true"
- android:autoRemoveFromRecents="true"
- android:showOnLockScreen="true"
- android:launchMode="singleTop"
- android:exported="false">
- </activity>
-
<activity android:name="com.android.systemui.communal.widgets.EditWidgetsActivity"
android:theme="@style/Theme.EditWidgetsActivity"
android:excludeFromRecents="true"
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 50ed7ab7c85a..7f16ca52d949 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,7 +23,8 @@ package {
default_visibility: [
"//visibility:override",
"//frameworks/base/packages/SystemUI:__subpackages__",
- "//platform_testing:__subpackages__"
+ "//frameworks/libs/systemui/tracinglib:__subpackages__",
+ "//platform_testing:__subpackages__",
],
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eaa6d07265ff..41d12dca869e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -45,6 +45,13 @@ flag {
}
flag {
+ name: "notifications_improved_hun_animation"
+ namespace: "systemui"
+ description: "Adds a translateY animation, and other improvements to match the motion specs of the HUN Intro + Outro animations."
+ bug: "243302608"
+}
+
+flag {
name: "notification_lifetime_extension_refactor"
namespace: "systemui"
description: "Enables moving notification lifetime extension management from SystemUI to "
@@ -198,6 +205,13 @@ flag {
}
flag {
+ name: "pss_task_switcher"
+ namespace: "systemui"
+ description: "Enable the task switcher feature for partial screen sharing"
+ bug: "317208379"
+}
+
+flag {
name: "rest_to_unlock"
namespace: "systemui"
description: "Require prolonged touch for fingerprint authentication"
@@ -254,3 +268,24 @@ flag {
"prefer using alpha to distinguish network activity."
bug: "310715220"
}
+
+flag {
+ name: "haptic_volume_slider"
+ namespace: "systemui"
+ description: "Adds haptic feedback to the volume slider."
+ bug: "316953430"
+}
+
+flag {
+ name: "screenshare_notification_hiding"
+ namespace: "systemui"
+ description: "Enable hiding of notifications during screenshare"
+ bug: "312784809"
+}
+
+flag {
+ name: "bluetooth_qs_tile_dialog_auto_on_toggle"
+ namespace: "systemui"
+ description: "Displays the auto on toggle in the bluetooth QS tile dialog"
+ bug: "316985153"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 2a9cf0fdc507..55fc3a2a81c1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,10 +20,12 @@ import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -31,11 +33,13 @@ 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.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
@@ -98,7 +102,6 @@ fun CommunalHub(
modifier = modifier.fillMaxSize().background(Color.White),
) {
CommunalHubLazyGrid(
- modifier = Modifier.align(Alignment.CenterStart),
communalContent = communalContent,
viewModel = viewModel,
contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
@@ -138,21 +141,21 @@ fun CommunalHub(
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
- modifier: Modifier = Modifier,
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
) {
- var gridModifier = modifier
+ var gridModifier = Modifier.align(Alignment.CenterStart)
val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
val contentListState = rememberContentListState(communalContent, viewModel)
list = contentListState.list
+ // for drag & drop operations within the communal hub grid
dragDropState =
rememberGridDragDropState(
gridState = gridState,
@@ -164,9 +167,22 @@ private fun CommunalHubLazyGrid(
.fillMaxSize()
.dragContainer(dragDropState, beforeContentPadding(contentPadding))
.onGloballyPositioned { setGridCoordinates(it) }
+ // for widgets dropped from other activities
+ val dragAndDropTargetState =
+ rememberDragAndDropTargetState(
+ gridState = gridState,
+ contentListState = contentListState,
+ updateDragPositionForRemove = updateDragPositionForRemove
+ )
+
+ // A full size box in background that listens to widget drops from the picker.
+ // Since the grid has its own listener for in-grid drag events, we use a separate element
+ // for android drag events.
+ Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
gridModifier = gridModifier.height(Dimensions.GridHeight)
}
+
LazyHorizontalGrid(
modifier = gridModifier,
state = gridState,
@@ -309,12 +325,24 @@ private fun CommunalContent(
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
}
}
+/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+@Composable
+fun WidgetPlaceholderContent(size: SizeF) {
+ Card(
+ modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ shape = RoundedCornerShape(16.dp)
+ ) {}
+}
+
@Composable
private fun WidgetContent(
model: CommunalContentModel.Widget,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 89c57658b474..979991d7dc2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -16,11 +16,10 @@
package com.android.systemui.communal.ui.compose
+import android.content.ComponentName
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
@@ -32,6 +31,7 @@ fun rememberContentListState(
return remember(communalContent) {
ContentListState(
communalContent,
+ viewModel::onAddWidget,
viewModel::onDeleteWidget,
viewModel::onReorderWidgets,
)
@@ -46,30 +46,57 @@ fun rememberContentListState(
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
+ private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
private val onDeleteWidget: (id: Int) -> Unit,
- private val onReorderWidgets: (ids: List<Int>) -> Unit,
+ private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
- var list by mutableStateOf(communalContent)
+ var list = communalContent.toMutableStateList()
private set
/** Move item to a new position in the list. */
fun onMove(fromIndex: Int, toIndex: Int) {
- list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
+ list.apply { add(toIndex, removeAt(fromIndex)) }
}
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
if (list[indexToRemove] is CommunalContentModel.Widget) {
val widget = list[indexToRemove] as CommunalContentModel.Widget
- list = list.toMutableList().apply { removeAt(indexToRemove) }
+ list.apply { removeAt(indexToRemove) }
onDeleteWidget(widget.appWidgetId)
}
}
- /** Persist the new order with all the movements happened during dragging. */
- fun onSaveList() {
- val widgetIds: List<Int> =
- list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId }
- onReorderWidgets(widgetIds)
+ /**
+ * Persists the new order with all the movements happened during drag operations & the new
+ * widget drop (if applicable).
+ *
+ * @param newItemComponentName name of the new widget that was dropped into the list; null if no
+ * new widget was added.
+ * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
+ * widget was dropped.
+ */
+ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
+ // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
+ // in the list. When persisted in DB, this leaves space for the new item (to be added) at
+ // the correct priority.
+ val widgetIdToPriorityMap: Map<Int, Int> =
+ list
+ .mapIndexedNotNull { index, item ->
+ if (item is CommunalContentModel.Widget) {
+ item.appWidgetId to list.size - index
+ } else {
+ null
+ }
+ }
+ .toMap()
+ // reorder and then add the new widget
+ onReorderWidgets(widgetIdToPriorityMap)
+ if (newItemComponentName != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
+ }
}
+
+ /** Returns true if the item at given index is editable. */
+ fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
new file mode 100644
index 000000000000..22aa8378cdd0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.view.DragEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+/**
+ * Holds state associated with dragging and dropping items from other activities into the lazy grid.
+ *
+ * @see dragAndDropTarget
+ */
+@Composable
+internal fun rememberDragAndDropTargetState(
+ gridState: LazyGridState,
+ contentListState: ContentListState,
+ updateDragPositionForRemove: (offset: Offset) -> Boolean,
+): DragAndDropTargetState {
+ val scope = rememberCoroutineScope()
+ val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
+ // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
+ // that allows differentiating intention of scrolling from intention of dragging over the first
+ // visible item.
+ val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+ val state =
+ remember(gridState, contentListState) {
+ DragAndDropTargetState(
+ state = gridState,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollSpeed = autoScrollSpeed,
+ autoScrollThreshold = autoScrollThreshold,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ }
+ LaunchedEffect(autoScrollSpeed.floatValue) {
+ if (autoScrollSpeed.floatValue != 0f) {
+ while (isActive) {
+ gridState.scrollBy(autoScrollSpeed.floatValue)
+ delay(10)
+ }
+ }
+ }
+ return state
+}
+
+/**
+ * Attaches a listener for drag and drop events from other activities.
+ *
+ * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
+ * @see DragEvent
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun Modifier.dragAndDropTarget(
+ dragDropTargetState: DragAndDropTargetState,
+): Modifier {
+ val state by rememberUpdatedState(dragDropTargetState)
+
+ return this then
+ Modifier.dragAndDropTarget(
+ shouldStartDragAndDrop = accept@{ startEvent ->
+ startEvent.mimeTypes().any { it == ClipDescription.MIMETYPE_TEXT_INTENT }
+ },
+ target =
+ object : DragAndDropTarget {
+ override fun onStarted(event: DragAndDropEvent) {
+ state.onStarted()
+ }
+
+ override fun onMoved(event: DragAndDropEvent) {
+ state.onMoved(event)
+ }
+
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return state.onDrop(event)
+ }
+
+ override fun onEnded(event: DragAndDropEvent) {
+ state.onEnded()
+ }
+ }
+ )
+}
+
+/**
+ * Handles dropping of an item coming from a different activity (e.g. widget picker) in to the grid
+ * corresponding to the provided [LazyGridState].
+ *
+ * Adds a placeholder container to highlight the anticipated location the widget will be dropped to.
+ * When the item is held over an empty area, the placeholder appears at the end of the grid if one
+ * didn't exist already. As user moves the item over an existing item, the placeholder appears in
+ * place of that existing item. And then, the existing item is pushed over as part of re-ordering.
+ *
+ * Once item is dropped, new ordering along with the dropped item is persisted. See
+ * [ContentListState.onSaveList].
+ *
+ * Difference between this and [GridDragDropState] is that, this is used for listening to drops from
+ * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
+ * the communal hub grid.
+ */
+internal class DragAndDropTargetState(
+ private val state: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val autoScrollSpeed: MutableState<Float>,
+ private val autoScrollThreshold: Float,
+ private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+) {
+ /**
+ * The placeholder item that is treated as if it is being dragged across the grid. It is added
+ * to grid once drag and drop event is started and removed when event ends.
+ */
+ private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+
+ private var placeHolderIndex: Int? = null
+ private var isOnRemoveButton = false
+
+ fun onStarted() {
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
+ placeHolderIndex = contentListState.list.size - 1
+ }
+
+ fun onMoved(event: DragAndDropEvent) {
+ val dragEvent = event.toAndroidDragEvent()
+ isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
+ if (!isOnRemoveButton) {
+ findTargetItem(dragEvent)?.apply {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
+ }
+
+ autoScrollIfNearEdges(dragEvent)
+
+ if (contentListState.isItemEditable(this.index)) {
+ movePlaceholderTo(this.index)
+ placeHolderIndex = this.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ }
+ }
+ }
+
+ fun onDrop(event: DragAndDropEvent): Boolean {
+ autoScrollSpeed.value = 0f
+ if (isOnRemoveButton) {
+ return false
+ }
+ return placeHolderIndex?.let { dropIndex ->
+ val componentName = event.maybeWidgetComponentName()
+ if (componentName != null) {
+ // Placeholder isn't removed yet to allow the setting the right priority for items
+ // before adding in the new item.
+ contentListState.onSaveList(
+ newItemComponentName = componentName,
+ newItemIndex = dropIndex
+ )
+ return@let true
+ }
+ return false
+ }
+ ?: false
+ }
+
+ fun onEnded() {
+ autoScrollSpeed.value = 0f
+ placeHolderIndex = null
+ contentListState.list.remove(placeHolder)
+ isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
+ }
+
+ private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragEvent.x
+ } else {
+ dragEvent.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportSize.width - dragEvent.x
+ } else {
+ state.layoutInfo.viewportSize.height - dragEvent.y
+ }
+ autoScrollSpeed.value =
+ when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold ->
+ -(autoScrollThreshold - distanceFromStart)
+ else -> 0f
+ }
+ }
+
+ private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
+ state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+ dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
+ dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
+ }
+
+ private fun movePlaceholderTo(index: Int) {
+ val currentIndex = contentListState.list.indexOf(placeHolder)
+ if (currentIndex != index) {
+ contentListState.onMove(currentIndex, index)
+ }
+ }
+
+ /**
+ * Parses and returns the component name of the widget that was dropped into the communal grid.
+ *
+ * Returns null if the drop event didn't include the widget information.
+ */
+ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
+ val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+ return clipData
+ ?.getItemAt(0)
+ ?.intent
+ ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5451d0550095..0d460aa8d464 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -32,15 +32,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
-import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
@@ -112,7 +110,7 @@ internal constructor(
.firstOrNull { item ->
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
(offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
(offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
}
@@ -149,7 +147,7 @@ internal constructor(
val targetItem =
state.layoutInfo.visibleItemsInfo.find { item ->
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
draggingItem.index != item.index
@@ -187,10 +185,6 @@ internal constructor(
private val LazyGridItemInfo.offsetEnd: IntOffset
get() = this.offset + this.size
- /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */
- private val LazyGridItemInfo.isEditable: Boolean
- get() = contentListState.list[this.index] is CommunalContentModel.Widget
-
/** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */
private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float {
return when {
@@ -210,14 +204,6 @@ internal constructor(
}
}
-private operator fun IntOffset.plus(size: IntSize): IntOffset {
- return IntOffset(x + size.width, y + size.height)
-}
-
-private operator fun Offset.plus(size: Size): Offset {
- return Offset(x + size.width, y + size.height)
-}
-
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
beforeContentPadding: ContentPaddingInPx
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
index ebf1beb132f0..b86c07e2e311 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
@@ -13,13 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.adapter
-/**
- * Temporary adapter class while
- * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
- * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
- *
- * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
- */
-interface UdfpsKeyguardViewControllerAdapter
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+
+/** Adds the given size to the x and y offsets in this [IntOffset] */
+operator fun IntOffset.plus(size: IntSize): IntOffset {
+ return IntOffset(x + size.width, y + size.height)
+}
+
+/** Adds the given size to the x and y offsets in this [Offset]. */
+operator fun Offset.plus(size: Size): Offset {
+ return Offset(x + size.width, y + size.height)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 000000000000..472484aa74d9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+ viewModel: KeyguardLongPressViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+ val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+ val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ enabled = isEnabled,
+ onLongClick = viewModel::onLongPress,
+ onClick = {},
+ interactionSource = interactionSource,
+ // Passing null for the indication removes the ripple effect.
+ indication = null,
+ )
+ .pointerInput(settingsMenuBounds) {
+ awaitEachGesture {
+ val pointerInputChange = awaitFirstDown()
+ if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+ viewModel.onTouchedOutside()
+ }
+ }
+ },
+ ) {
+ content(setSettingsMenuBounds)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec8f7ed..67a68200f269 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalFoundationApi::class)
-
package com.android.systemui.keyguard.ui.composable
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@ class LockscreenScene
constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
- @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
private val lockscreenContent: Lazy<LockscreenContent>,
+ private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -93,9 +72,8 @@ constructor(
modifier: Modifier,
) {
LockscreenScene(
- viewProvider = viewProvider,
- viewModel = viewModel,
lockscreenContent = lockscreenContent,
+ viewBasedLockscreenContent = viewBasedLockscreenContent,
modifier = modifier,
)
}
@@ -116,98 +94,21 @@ constructor(
@Composable
private fun SceneScope.LockscreenScene(
- viewProvider: () -> View,
- viewModel: LockscreenSceneViewModel,
lockscreenContent: Lazy<LockscreenContent>,
+ viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
modifier: Modifier = Modifier,
) {
- fun findSettingsMenu(): View {
- return viewProvider().requireViewById(R.id.keyguard_settings_button)
- }
-
- Box(
- modifier = modifier,
- ) {
- LongPressSurface(
- viewModel = viewModel.longPress,
- isSettingsMenuVisible = { findSettingsMenu().isVisible },
- settingsMenuBounds = {
- val bounds = android.graphics.Rect()
- findSettingsMenu().getHitRect(bounds)
- bounds.toComposeRect()
- },
- modifier = Modifier.fillMaxSize(),
- )
-
- if (UseLockscreenContent) {
- lockscreenContent
- .get()
- .Content(
- modifier = Modifier.fillMaxSize(),
- )
- } else {
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy
- // code just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = modifier.fillMaxSize(),
+ )
+ } else {
+ with(viewBasedLockscreenContent.get()) {
+ Content(
+ modifier = modifier.fillMaxSize(),
)
- }
-
- val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
- Layout(
- modifier = Modifier.fillMaxSize(),
- content = {
- NotificationStack(
- viewModel = viewModel.notifications,
- isScrimVisible = false,
- )
- }
- ) { measurables, constraints ->
- check(measurables.size == 1)
- val height = notificationStackPosition.height.toInt()
- val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
- val placeable = measurables[0].measure(childConstraints)
- layout(constraints.maxWidth, constraints.maxHeight) {
- val start = (constraints.maxWidth - placeable.measuredWidth) / 2
- placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
- }
}
}
}
-
-@Composable
-private fun LongPressSurface(
- viewModel: KeyguardLongPressViewModel,
- isSettingsMenuVisible: () -> Boolean,
- settingsMenuBounds: () -> Rect,
- modifier: Modifier = Modifier,
-) {
- val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
- Box(
- modifier =
- modifier
- .combinedClickable(
- enabled = isEnabled,
- onLongClick = viewModel::onLongPress,
- onClick = {},
- )
- .pointerInput(Unit) {
- awaitEachGesture {
- val pointerInputChange = awaitFirstDown()
- if (
- isSettingsMenuVisible() &&
- !settingsMenuBounds().contains(pointerInputChange.position)
- ) {
- viewModel.onTouchedOutside()
- }
- }
- },
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 000000000000..976161b3beb7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenSceneViewModel,
+ @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+ @Composable
+ fun SceneScope.Content(
+ modifier: Modifier = Modifier,
+ ) {
+ fun findSettingsMenu(): View {
+ return viewProvider().requireViewById(R.id.keyguard_settings_button)
+ }
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+
+ val notificationStackPosition by
+ viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+ Layout(
+ modifier =
+ Modifier.fillMaxSize().onPlaced {
+ val settingsMenuView = findSettingsMenu()
+ onSettingsMenuPlaced(
+ if (settingsMenuView.isVisible) {
+ val bounds = Rect()
+ settingsMenuView.getHitRect(bounds)
+ bounds.toComposeRect()
+ } else {
+ null
+ }
+ )
+ },
+ content = {
+ NotificationStack(
+ viewModel = viewModel.notifications,
+ isScrimVisible = false,
+ )
+ }
+ ) { measurables, constraints ->
+ check(measurables.size == 1)
+ val height = notificationStackPosition.height.toInt()
+ val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+ val placeable = measurables[0].measure(childConstraints)
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+ placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
new file mode 100644
index 000000000000..efa8cc7c2c55
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.ui.layout.HorizontalAlignmentLine
+import androidx.compose.ui.layout.VerticalAlignmentLine
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Encapsulates all blueprint alignment lines.
+ *
+ * These can be used to communicate alignment lines emitted by elements that the blueprint should
+ * consume and use to know how to constrain and/or place other elements in that blueprint.
+ *
+ * For more information, please see
+ * [the official documentation](https://developer.android.com/jetpack/compose/layouts/alignment-lines).
+ */
+object BlueprintAlignmentLines {
+
+ /**
+ * Encapsulates alignment lines produced by the lock icon element.
+ *
+ * Because the lock icon is also the same element as the under-display fingerprint sensor
+ * (UDFPS), blueprints should use its alignment lines to make sure that other elements on screen
+ * do not overlap with the lock icon.
+ */
+ object LockIcon {
+
+ /** The left edge of the lock icon. */
+ val Left =
+ VerticalAlignmentLine(
+ merger = { old, new ->
+ // When two left alignment line values are provided, choose the leftmost one:
+ min(old, new)
+ },
+ )
+
+ /** The top edge of the lock icon. */
+ val Top =
+ HorizontalAlignmentLine(
+ merger = { old, new ->
+ // When two top alignment line values are provided, choose the topmost one:
+ min(old, new)
+ },
+ )
+
+ /** The right edge of the lock icon. */
+ val Right =
+ VerticalAlignmentLine(
+ merger = { old, new ->
+ // When two right alignment line values are provided, choose the rightmost one:
+ max(old, new)
+ },
+ )
+
+ /** The bottom edge of the lock icon. */
+ val Bottom =
+ HorizontalAlignmentLine(
+ merger = { old, new ->
+ // When two bottom alignment line values are provided, choose the bottommost
+ // one:
+ max(old, new)
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf9ba4b..86124c635684 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
/** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "communal"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): communal blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
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 fc1df848d46f..d9d98cbd2da6 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
@@ -16,23 +16,21 @@
package com.android.systemui.keyguard.ui.composable.blueprint
-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.offset
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.unit.IntOffset
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
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.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
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.viewmodel.LockscreenContentViewModel
@@ -56,53 +54,125 @@ constructor(
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "default"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- val context = LocalContext.current
- val lockIconBounds = lockSection.lockIconBounds(context)
val isUdfpsVisible = viewModel.isUdfpsVisible
- Box(
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
}
- }
- }
- with(lockSection) {
- LockIcon(
- modifier =
- Modifier.width { lockIconBounds.width() }
- .height { lockIconBounds.height() }
- .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) }
- )
- }
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
- Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
}
- }
- with(bottomAreaSection) { BottomArea(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
+ )
+ )
+ 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,
+ )
+ }
}
}
}
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 fa913f1695fe..4704f5c3d1eb 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
@@ -16,38 +16,28 @@
package com.android.systemui.keyguard.ui.composable.blueprint
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
+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.padding
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.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.width
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
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.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
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.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
-import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -64,100 +54,130 @@ constructor(
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- val context = LocalContext.current
- val lockIconBounds = lockSection.lockIconBounds(context)
val isUdfpsVisible = viewModel.isUdfpsVisible
- Box(
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
modifier = modifier,
- ) {
- Column(
- modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- val shortcutSizePx =
- with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() }
-
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier =
- Modifier.fillMaxWidth().offset {
- val rowTop =
- if (shortcutSizePx.height > lockIconBounds.height()) {
- (lockIconBounds.top -
- (shortcutSizePx.height + lockIconBounds.height()) / 2)
- .roundToInt()
- } else {
- lockIconBounds.top
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
}
+ }
+ }
- IntOffset(0, rowTop)
- },
- ) {
- Spacer(Modifier.weight(1f))
+ // Constrained to the left of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
- with(bottomAreaSection) { Shortcut(isStart = true) }
+ with(lockSection) { LockIcon() }
- Spacer(Modifier.weight(1f))
+ // Constrained to the right of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
- with(lockSection) {
- LockIcon(
- modifier =
- Modifier.width { lockIconBounds.width() }
- .height { lockIconBounds.height() }
- )
- }
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
- Spacer(Modifier.weight(1f))
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
- with(bottomAreaSection) { Shortcut(isStart = false) }
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val startSideShortcutMeasurable = measurables[1]
+ val lockIconMeasurable = measurables[2]
+ val endSideShortcutMeasurable = measurables[3]
+ val belowLockIconMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
- Spacer(Modifier.weight(1f))
- }
+ 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],
+ )
- Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val startSideShortcutPlaceable =
+ startSideShortcutMeasurable.measure(noMinConstraints)
+ val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
- with(bottomAreaSection) {
- IndicationArea(
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- horizontal =
- dimensionResource(
- R.dimen.keyguard_affordance_horizontal_offset
- )
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_affordance_vertical_offset
- )
- )
- .heightIn(min = shortcutSizeDp().height),
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ startSideShortcutPlaceable.placeRelative(
+ x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ endSideShortcutPlaceable.placeRelative(
+ x =
+ lockIconBounds.right +
+ (constraints.maxWidth - lockIconBounds.right) / 2 -
+ endSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5fcc2b3..fdf11668ae76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@ import javax.inject.Inject
* Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
* tablet form factor).
*/
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
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 53e4be34dcfa..db20f65ee78d 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
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.composable.section
import android.view.View
import android.widget.ImageView
import androidx.annotation.IdRes
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -58,38 +57,17 @@ constructor(
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
) {
- @Composable
- fun SceneScope.BottomArea(
- modifier: Modifier = Modifier,
- ) {
- Row(
- modifier =
- modifier
- .padding(
- horizontal =
- dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
- )
- .padding(
- bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset)
- ),
- ) {
- Shortcut(
- isStart = true,
- )
-
- IndicationArea(
- modifier = Modifier.weight(1f),
- )
-
- Shortcut(
- isStart = false,
- )
- }
- }
-
+ /**
+ * Renders a single lockscreen shortcut.
+ *
+ * @param isStart Whether the shortcut goes on the left (in left-to-right locales).
+ * @param applyPadding Whether to apply padding around the shortcut, this is needed if the
+ * shortcut is placed along the edges of the display.
+ */
@Composable
fun SceneScope.Shortcut(
isStart: Boolean,
+ applyPadding: Boolean,
modifier: Modifier = Modifier,
) {
MovableElement(
@@ -103,6 +81,12 @@ constructor(
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
indicationController = indicationController,
+ modifier =
+ if (applyPadding) {
+ Modifier.shortcutPadding()
+ } else {
+ Modifier
+ }
)
}
}
@@ -113,7 +97,7 @@ constructor(
) {
MovableElement(
key = IndicationAreaElementKey,
- modifier = modifier,
+ modifier = modifier.shortcutPadding(),
) {
IndicationArea(
indicationAreaViewModel = indicationAreaViewModel,
@@ -218,6 +202,14 @@ constructor(
modifier = modifier.fillMaxWidth(),
)
}
+
+ @Composable
+ private fun Modifier.shortcutPadding(): Modifier {
+ return this.padding(
+ horizontal = dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+ )
+ .padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
+ }
}
private val StartButtonElementKey = ElementKey("StartButton")
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 8bbe424b9a49..2a6bea75791c 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
@@ -17,23 +17,35 @@
package com.android.systemui.keyguard.ui.composable.section
import android.content.Context
-import android.graphics.Point
-import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
import javax.inject.Inject
class LockSection
@@ -42,23 +54,70 @@ constructor(
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
+ private val lockIconViewController: Lazy<LockIconViewController>,
+ private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+ private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+ private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
- MovableElement(
- key = LockIconElementKey,
- modifier = modifier,
- ) {
- Box(
- modifier = Modifier.background(Color.Red),
- ) {
- Text(
- text = "TODO(b/316211368): Lock",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
+ if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ return
}
+
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ val view =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ DeviceEntryIconView(context, null).apply {
+ id = R.id.device_entry_icon_view
+ DeviceEntryIconViewBinder.bind(
+ this,
+ deviceEntryIconViewModel.get(),
+ deviceEntryForegroundViewModel.get(),
+ deviceEntryBackgroundViewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
+ )
+ }
+ } else {
+ // keyguardBottomAreaRefactor()
+ LockIconView(context, null).apply {
+ id = R.id.lock_icon_view
+ lockIconViewController.get().setLockIconView(this)
+ }
+ }
+ view
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
}
/**
@@ -67,9 +126,9 @@ constructor(
* On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
* the same as the bounds of the sensor.
*/
- fun lockIconBounds(
+ private fun lockIconBounds(
context: Context,
- ): Rect {
+ ): IntRect {
val windowViewBounds = windowManager.currentWindowMetrics.bounds
var widthPx = windowViewBounds.right.toFloat()
if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
@@ -85,36 +144,33 @@ constructor(
val lockIconRadiusPx = (defaultDensity * 36).toInt()
val udfpsLocation = authController.udfpsLocation
- return if (authController.isUdfpsSupported && udfpsLocation != null) {
- centerLockIcon(udfpsLocation, authController.udfpsRadius)
- } else {
- val scaleFactor = authController.scaleFactor
- val bottomPaddingPx =
- context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
- val heightPx = windowViewBounds.bottom.toFloat()
+ val (center, radius) =
+ if (authController.isUdfpsSupported && udfpsLocation != null) {
+ Pair(
+ IntOffset(
+ x = udfpsLocation.x,
+ y = udfpsLocation.y,
+ ),
+ authController.udfpsRadius.toInt(),
+ )
+ } else {
+ val scaleFactor = authController.scaleFactor
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ val heightPx = windowViewBounds.bottom.toFloat()
- centerLockIcon(
- Point(
- (widthPx / 2).toInt(),
- (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
- ),
- lockIconRadiusPx * scaleFactor
- )
- }
- }
+ Pair(
+ IntOffset(
+ x = (widthPx / 2).toInt(),
+ y =
+ (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
+ .toInt(),
+ ),
+ (lockIconRadiusPx * scaleFactor).toInt(),
+ )
+ }
- private fun centerLockIcon(
- center: Point,
- radius: Float,
- ): Rect {
- return Rect().apply {
- set(
- center.x - radius.toInt(),
- center.y - radius.toInt(),
- center.x + radius.toInt(),
- center.y + radius.toInt(),
- )
- }
+ return IntRect(center, radius)
}
}
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 f135be260cbd..c547e2b93158 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
@@ -16,36 +16,25 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+ private val viewModel: NotificationsPlaceholderViewModel,
+) {
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- MovableElement(
- key = NotificationsElementKey,
+ NotificationStack(
+ viewModel = viewModel,
+ isScrimVisible = false,
modifier = modifier,
- ) {
- Box(
- modifier = Modifier.fillMaxSize().background(Color.Yellow),
- ) {
- Text(
- text = "TODO(b/316211368): Notifications",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
+ )
}
}
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 000000000000..44b0535efb57
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+ private val viewModel: KeyguardSettingsMenuViewModel,
+ private val longPressViewModel: KeyguardLongPressViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val activityStarter: ActivityStarter,
+) {
+ @Composable
+ @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+ fun SettingsMenu(
+ onPlaced: (Rect?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposableHandle, setDisposableHandle) =
+ remember { mutableStateOf<DisposableHandle?>(null) }
+ AndroidView(
+ factory = { context ->
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_settings_popup_menu,
+ null,
+ )
+ .apply {
+ isVisible = false
+ alpha = 0f
+
+ setDisposableHandle(
+ KeyguardSettingsViewBinder.bind(
+ view = this,
+ viewModel = viewModel,
+ longPressViewModel = longPressViewModel,
+ rootViewModel = null,
+ vibratorHelper = vibratorHelper,
+ activityStarter = activityStarter,
+ )
+ )
+ }
+ },
+ onRelease = { disposableHandle?.dispose() },
+ modifier =
+ modifier
+ .padding(
+ bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+ )
+ .padding(
+ horizontal =
+ dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+ )
+ .onPlaced { coordinates ->
+ onPlaced(
+ if (!coordinates.size.toSize().isEmpty()) {
+ Rect(coordinates.positionInParent(), coordinates.size.toSize())
+ } else {
+ null
+ }
+ )
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b9d66432b5f2..2bfa7d9bbd7b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,6 +33,8 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import java.io.PrintWriter
@@ -51,12 +53,13 @@ class AnimatableClockView @JvmOverloads constructor(
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- var messageBuffer: MessageBuffer? = null
- set(value) {
- logger = if (value != null) Logger(value, TAG) else null
- }
-
- private var logger: Logger? = null
+ // To protect us from issues from this being null while the TextView constructor is running, we
+ // implement the get method and ensure a value is returned before initialization is complete.
+ private var logger = DEFAULT_LOGGER
+ get() = field ?: DEFAULT_LOGGER
+ var messageBuffer: MessageBuffer
+ get() = logger.buffer
+ set(value) { logger = Logger(value, TAG) }
private val time = Calendar.getInstance()
@@ -133,8 +136,8 @@ class AnimatableClockView @JvmOverloads constructor(
}
override fun onAttachedToWindow() {
+ logger.d("onAttachedToWindow")
super.onAttachedToWindow()
- logger?.d("onAttachedToWindow")
refreshFormat()
}
@@ -150,13 +153,13 @@ class AnimatableClockView @JvmOverloads constructor(
time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
contentDescription = DateFormat.format(descFormat, time)
val formattedText = DateFormat.format(format, time)
- logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
+ logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
// Setting text actually triggers a layout pass (because the text view is set to
// wrap_content width and TextView always relayouts for this). Avoid needless
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
- logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+ logger.d({ "refreshTime: done setting new time text to: $str1" }) {
str1 = formattedText?.toString()
}
// Because the TextLayout may mutate under the hood as a result of the new text, we
@@ -165,21 +168,22 @@ class AnimatableClockView @JvmOverloads constructor(
// without being notified TextInterpolator being notified.
if (layout != null) {
textAnimator?.updateLayout(layout)
- logger?.d("refreshTime: done updating textAnimator layout")
+ logger.d("refreshTime: done updating textAnimator layout")
}
requestLayout()
- logger?.d("refreshTime: after requestLayout")
+ logger.d("refreshTime: after requestLayout")
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
+ logger.d({ "onTimeZoneChanged($str1)" }) { str1 = timeZone?.toString() }
time.timeZone = timeZone
refreshFormat()
- logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
}
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ logger.d("onMeasure")
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val animator = textAnimator
if (animator == null) {
@@ -189,10 +193,10 @@ class AnimatableClockView @JvmOverloads constructor(
} else {
animator.updateLayout(layout)
}
- logger?.d("onMeasure")
}
override fun onDraw(canvas: Canvas) {
+ logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
// Use textAnimator to render text if animation is enabled.
// Otherwise default to using standard draw functions.
if (isAnimationEnabled) {
@@ -201,22 +205,23 @@ class AnimatableClockView @JvmOverloads constructor(
} else {
super.onDraw(canvas)
}
- logger?.d("onDraw")
}
override fun invalidate() {
+ @Suppress("UNNECESSARY_SAFE_CALL")
+ // logger won't be initialized when called by TextView's constructor
+ logger.d("invalidate")
super.invalidate()
- logger?.d("invalidate")
}
override fun onTextChanged(
- text: CharSequence,
- start: Int,
- lengthBefore: Int,
- lengthAfter: Int
+ text: CharSequence,
+ start: Int,
+ lengthBefore: Int,
+ lengthAfter: Int
) {
+ logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
super.onTextChanged(text, start, lengthBefore, lengthAfter)
- logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
}
fun setLineSpacingScale(scale: Float) {
@@ -230,7 +235,7 @@ class AnimatableClockView @JvmOverloads constructor(
}
fun animateColorChange() {
- logger?.d("animateColorChange")
+ logger.d("animateColorChange")
setTextStyle(
weight = lockScreenWeight,
textSize = -1f,
@@ -252,7 +257,7 @@ class AnimatableClockView @JvmOverloads constructor(
}
fun animateAppearOnLockscreen() {
- logger?.d("animateAppearOnLockscreen")
+ logger.d("animateAppearOnLockscreen")
setTextStyle(
weight = dozingWeight,
textSize = -1f,
@@ -278,7 +283,7 @@ class AnimatableClockView @JvmOverloads constructor(
if (isAnimationEnabled && textAnimator == null) {
return
}
- logger?.d("animateFoldAppear")
+ logger.d("animateFoldAppear")
setTextStyle(
weight = lockScreenWeightInternal,
textSize = -1f,
@@ -305,7 +310,7 @@ class AnimatableClockView @JvmOverloads constructor(
// Skip charge animation if dozing animation is already playing.
return
}
- logger?.d("animateCharge")
+ logger.d("animateCharge")
val startAnimPhase2 = Runnable {
setTextStyle(
weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -329,7 +334,7 @@ class AnimatableClockView @JvmOverloads constructor(
}
fun animateDoze(isDozing: Boolean, animate: Boolean) {
- logger?.d("animateDoze")
+ logger.d("animateDoze")
setTextStyle(
weight = if (isDozing) dozingWeight else lockScreenWeight,
textSize = -1f,
@@ -448,7 +453,7 @@ class AnimatableClockView @JvmOverloads constructor(
isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
else -> DOUBLE_LINE_FORMAT_12_HOUR
}
- logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
+ logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
refreshTime()
@@ -552,6 +557,8 @@ class AnimatableClockView @JvmOverloads constructor(
companion object {
private val TAG = AnimatableClockView::class.simpleName!!
+ private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG)
+
const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cdd074d872c0..41bde5298c66 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,20 +21,16 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
-import android.util.Log
import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogMessageImpl
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
@@ -77,32 +73,6 @@ private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGet
return result ?: value
}
-private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
-
-private inline fun Logger?.tryLog(
- tag: String,
- level: LogLevel,
- messageInitializer: MessageInitializer,
- noinline messagePrinter: MessagePrinter,
- ex: Throwable? = null,
-) {
- if (this != null) {
- // Wrap messagePrinter to convert it from crossinline to noinline
- this.log(level, messagePrinter, ex, messageInitializer)
- } else {
- messageInitializer(TMP_MESSAGE)
- val msg = messagePrinter(TMP_MESSAGE)
- when (level) {
- LogLevel.VERBOSE -> Log.v(tag, msg, ex)
- LogLevel.DEBUG -> Log.d(tag, msg, ex)
- LogLevel.INFO -> Log.i(tag, msg, ex)
- LogLevel.WARNING -> Log.w(tag, msg, ex)
- LogLevel.ERROR -> Log.e(tag, msg, ex)
- LogLevel.WTF -> Log.wtf(tag, msg, ex)
- }
- }
-}
-
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -114,12 +84,15 @@ open class ClockRegistry(
val handleAllUsers: Boolean,
defaultClockProvider: ClockProvider,
val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
- messageBuffer: MessageBuffer? = null,
+ val clockBuffers: ClockMessageBuffers? = null,
val keepAllLoaded: Boolean,
subTag: String,
var isTransitClockEnabled: Boolean = false,
) {
private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
+ private val logger: Logger =
+ Logger(clockBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG)
+
interface ClockChangeListener {
// Called when the active clock changes
fun onCurrentClockChanged() {}
@@ -128,7 +101,6 @@ open class ClockRegistry(
fun onAvailableClocksChanged() {}
}
- private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
@@ -157,21 +129,15 @@ open class ClockRegistry(
val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
if (knownClocks == null) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = manager.getPackage() },
- { "Loading unrecognized clock package: $str1" }
- )
+ logger.w({ "Loading unrecognized clock package: $str1" }) {
+ str1 = manager.getPackage()
+ }
return true
}
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- { str1 = manager.getPackage() },
- { "Skipping initial load of known clock package package: $str1" }
- )
+ logger.i({ "Skipping initial load of known clock package package: $str1" }) {
+ str1 = manager.getPackage()
+ }
var isCurrentClock = false
var isClockListChanged = false
@@ -185,19 +151,14 @@ open class ClockRegistry(
}
if (manager != info.manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on attach: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on attach: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ }
continue
}
@@ -219,6 +180,8 @@ open class ClockRegistry(
pluginContext: Context,
manager: PluginLifecycleManager<ClockProviderPlugin>
) {
+ plugin.initialize(clockBuffers)
+
var isClockListChanged = false
for (clock in plugin.getClocks()) {
val id = clock.clockId
@@ -233,19 +196,14 @@ open class ClockRegistry(
}
if (manager != info.manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on load: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on load: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ }
manager.unloadPlugin()
continue
}
@@ -268,19 +226,14 @@ open class ClockRegistry(
val id = clock.clockId
val info = availableClocks[id]
if (info?.manager != manager) {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- {
- str1 = id
- str2 = info?.manager.toString()
- str3 = manager.toString()
- },
- {
- "Clock Id conflict on unload: " +
- "$str1 is double registered by $str2 and $str3"
- }
- )
+ logger.e({
+ "Clock Id conflict on unload: " +
+ "$str1 is double registered by $str2 and $str3"
+ }) {
+ str1 = id
+ str2 = info?.manager.toString()
+ str3 = manager.toString()
+ }
continue
}
info.provider = null
@@ -350,7 +303,7 @@ open class ClockRegistry(
ClockSettings.deserialize(json)
} catch (ex: Exception) {
- logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+ logger.e("Failed to parse clock settings", ex)
null
}
settings = result
@@ -379,7 +332,7 @@ open class ClockRegistry(
)
}
} catch (ex: Exception) {
- logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+ logger.e("Failed to set clock settings", ex)
}
settings = value
}
@@ -451,7 +404,8 @@ open class ClockRegistry(
}
init {
- // Register default clock designs
+ // Initialize & register default clock designs
+ defaultClockProvider.initialize(clockBuffers)
for (clock in defaultClockProvider.getClocks()) {
availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
}
@@ -514,12 +468,7 @@ open class ClockRegistry(
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
- logger.tryLog(
- TAG,
- LogLevel.VERBOSE,
- {},
- { "verifyLoadedProviders: shouldSchedule=false" }
- )
+ logger.v("verifyLoadedProviders: shouldSchedule=false")
return
}
@@ -528,12 +477,7 @@ open class ClockRegistry(
synchronized(availableClocks) {
isQueued.set(false)
if (keepAllLoaded) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: keepAllLoaded=true" }
- )
+ logger.i("verifyLoadedProviders: keepAllLoaded=true")
// Enforce that all plugins are loaded if requested
for ((_, info) in availableClocks) {
info.manager?.loadPlugin()
@@ -543,12 +487,7 @@ open class ClockRegistry(
val currentClock = availableClocks[currentClockId]
if (currentClock == null) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: currentClock=null" }
- )
+ logger.i("verifyLoadedProviders: currentClock=null")
// Current Clock missing, load no plugins and use default
for ((_, info) in availableClocks) {
info.manager?.unloadPlugin()
@@ -556,12 +495,7 @@ open class ClockRegistry(
return@launch
}
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- {},
- { "verifyLoadedProviders: load currentClock" }
- )
+ logger.i("verifyLoadedProviders: load currentClock")
val currentManager = currentClock.manager
currentManager?.loadPlugin()
@@ -577,30 +511,26 @@ open class ClockRegistry(
private fun onConnected(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
}
private fun onLoaded(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
if (isCurrent) {
triggerOnCurrentClockChanged()
@@ -609,16 +539,14 @@ open class ClockRegistry(
private fun onUnloaded(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
if (isCurrent) {
triggerOnCurrentClockChanged()
@@ -627,16 +555,14 @@ open class ClockRegistry(
private fun onDisconnected(info: ClockInfo) {
val isCurrent = currentClockId == info.metadata.clockId
- logger.tryLog(
- TAG,
+ logger.log(
if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
- {
- str1 = info.metadata.clockId
- str2 = info.manager.toString()
- bool1 = isCurrent
- },
{ "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
- )
+ ) {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ }
}
fun getClocks(): List<ClockMetadata> {
@@ -676,23 +602,13 @@ open class ClockRegistry(
if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
- logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
+ logger.i({ "Rendering clock $str1" }) { str1 = clockId }
return clock
} else if (availableClocks.containsKey(clockId)) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = clockId },
- { "Clock $str1 not loaded; using default" }
- )
+ logger.w({ "Clock $str1 not loaded; using default" }) { str1 = clockId }
verifyLoadedProviders()
} else {
- logger.tryLog(
- TAG,
- LogLevel.ERROR,
- { str1 = clockId },
- { "Clock $str1 not found; using default" }
- )
+ logger.e({ "Clock $str1 not found; using default" }) { str1 = clockId }
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 01c03b1f25f6..99d321695d04 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
import com.android.systemui.plugins.clocks.WeatherData
@@ -41,8 +42,6 @@ import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
-private val TAG = DefaultClockController::class.simpleName
-
/**
* Controls the default clock visuals.
*
@@ -56,6 +55,7 @@ class DefaultClockController(
private val settings: ClockSettings?,
private val hasStepClockAnimation: Boolean = false,
private val migratedClocks: Boolean = false,
+ messageBuffers: ClockMessageBuffers? = null,
) : ClockController {
override val smallClock: DefaultClockFaceController
override val largeClock: LargeClockFaceController
@@ -83,13 +83,15 @@ class DefaultClockController(
DefaultClockFaceController(
layoutInflater.inflate(R.layout.clock_default_small, parent, false)
as AnimatableClockView,
- settings?.seedColor
+ settings?.seedColor,
+ messageBuffers?.smallClockMessageBuffer
)
largeClock =
LargeClockFaceController(
layoutInflater.inflate(R.layout.clock_default_large, parent, false)
as AnimatableClockView,
- settings?.seedColor
+ settings?.seedColor,
+ messageBuffers?.largeClockMessageBuffer
)
clocks = listOf(smallClock.view, largeClock.view)
@@ -110,6 +112,7 @@ class DefaultClockController(
open inner class DefaultClockFaceController(
override val view: AnimatableClockView,
var seedColor: Int?,
+ messageBuffer: MessageBuffer?,
) : ClockFaceController {
// MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -120,12 +123,6 @@ class DefaultClockController(
override val config = ClockFaceConfig()
override val layout = DefaultClockFaceLayout(view)
- override var messageBuffer: MessageBuffer?
- get() = view.messageBuffer
- set(value) {
- view.messageBuffer = value
- }
-
override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
internal set
@@ -134,6 +131,7 @@ class DefaultClockController(
currentColor = seedColor!!
}
view.setColors(DOZE_COLOR, currentColor)
+ messageBuffer?.let { view.messageBuffer = it }
}
override val events =
@@ -188,7 +186,8 @@ class DefaultClockController(
inner class LargeClockFaceController(
view: AnimatableClockView,
seedColor: Int?,
- ) : DefaultClockFaceController(view, seedColor) {
+ messageBuffer: MessageBuffer?,
+ ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
override val layout = DefaultClockFaceLayout(view)
override val config =
ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index a219be53bd1a..20f87a059656 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -20,6 +20,7 @@ import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -35,6 +36,12 @@ class DefaultClockProvider(
val hasStepClockAnimation: Boolean = false,
val migratedClocks: Boolean = false
) : ClockProvider {
+ private var messageBuffers: ClockMessageBuffers? = null
+
+ override fun initialize(buffers: ClockMessageBuffers?) {
+ messageBuffers = buffers
+ }
+
override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
override fun createClock(settings: ClockSettings): ClockController {
@@ -49,6 +56,7 @@ class DefaultClockProvider(
settings,
hasStepClockAnimation,
migratedClocks,
+ messageBuffers,
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 0b7c3f987db2..4b21105a20a0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -17,56 +17,39 @@
package com.android.systemui.shared.notifications.data.repository
import android.provider.Settings
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-/** Provides access to state related to notifications. */
+/** Provides access to state related to notification settings. */
class NotificationSettingsRepository(
scope: CoroutineScope,
private val backgroundDispatcher: CoroutineDispatcher,
private val secureSettingsRepository: SecureSettingsRepository,
) {
/** The current state of the notification setting. */
- val settings: SharedFlow<NotificationSettingsModel> =
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
secureSettingsRepository
.intSetting(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
)
- .map { lockScreenShowNotificationsInt ->
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled = lockScreenShowNotificationsInt == 1,
- )
- }
- .shareIn(
+ .map { it == 1 }
+ .stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(),
- replay = 1,
+ initialValue = false,
)
- suspend fun getSettings(): NotificationSettingsModel {
- return withContext(backgroundDispatcher) {
- NotificationSettingsModel(
- isShowNotificationsOnLockScreenEnabled =
- secureSettingsRepository.get(
- name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- defaultValue = 0,
- ) == 1
- )
- }
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
- value = if (model.isShowNotificationsOnLockScreenEnabled) 1 else 0,
+ value = if (enabled) 1 else 0,
)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
index 21f3acaf15ff..9ec6ec8d3811 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt
@@ -17,32 +17,23 @@
package com.android.systemui.shared.notifications.domain.interactor
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
-import com.android.systemui.shared.notifications.shared.model.NotificationSettingsModel
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Encapsulates business logic for interacting with notification settings. */
class NotificationSettingsInteractor(
private val repository: NotificationSettingsRepository,
) {
- /** The current state of the notification setting. */
- val settings: Flow<NotificationSettingsModel> = repository.settings
+ /** Should notifications be visible on the lockscreen? */
+ val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> =
+ repository.isShowNotificationsOnLockScreenEnabled
- /** Toggles the setting to show or hide notifications on the lock screen. */
- suspend fun toggleShowNotificationsOnLockScreenEnabled() {
- val currentModel = repository.getSettings()
- setSettings(
- currentModel.copy(
- isShowNotificationsOnLockScreenEnabled =
- !currentModel.isShowNotificationsOnLockScreenEnabled,
- )
- )
- }
-
- suspend fun setSettings(model: NotificationSettingsModel) {
- repository.setSettings(model)
+ suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
+ repository.setShowNotificationsOnLockscreenEnabled(enabled)
}
- suspend fun getSettings(): NotificationSettingsModel {
- return repository.getSettings()
+ /** Toggles the setting to show or hide notifications on the lock screen. */
+ suspend fun toggleShowNotificationsOnLockscreenEnabled() {
+ val current = repository.isShowNotificationsOnLockScreenEnabled.value
+ repository.setShowNotificationsOnLockscreenEnabled(!current)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8a4b23..754d5dc0c9c6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@ interface SecureSettingsRepository {
): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun set(
+ suspend fun setInt(
name: String,
value: Int,
)
- suspend fun get(
+ suspend fun getInt(
name: String,
defaultValue: Int = 0,
): Int
+
+ suspend fun getString(name: String): String?
}
class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@ class SecureSettingsRepositoryImpl(
.flowOn(backgroundDispatcher)
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
withContext(backgroundDispatcher) {
Settings.Secure.putInt(
contentResolver,
@@ -90,7 +92,7 @@ class SecureSettingsRepositoryImpl(
}
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return withContext(backgroundDispatcher) {
Settings.Secure.getInt(
contentResolver,
@@ -99,4 +101,13 @@ class SecureSettingsRepositoryImpl(
)
}
}
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a0745751..37b97926afa4 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@ class FakeSecureSettingsRepository : SecureSettingsRepository {
return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return settings.value[name]?.toInt() ?: defaultValue
}
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
new file mode 100644
index 000000000000..006b521ad867
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import android.util.Log
+import com.android.systemui.log.LogMessageImpl
+
+/**
+ * A simple implementation of [MessageBuffer] that forwards messages to [android.util.Log]
+ * immediately. This defeats the intention behind [LogBuffer] and should only be used when
+ * [LogBuffer]s are unavailable in a certain context.
+ */
+class LogcatOnlyMessageBuffer(
+ val targetLogLevel: LogLevel,
+) : MessageBuffer {
+ private val singleMessage = LogMessageImpl.Factory.create()
+ private var isObtained: Boolean = false
+
+ @Synchronized
+ override fun obtain(
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable?,
+ ): LogMessage {
+ if (isObtained) {
+ throw UnsupportedOperationException(
+ "Message has already been obtained. Call order is incorrect."
+ )
+ }
+
+ singleMessage.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
+ isObtained = true
+ return singleMessage
+ }
+
+ @Synchronized
+ override fun commit(message: LogMessage) {
+ if (singleMessage != message) {
+ throw IllegalArgumentException("Message argument is not the expected message.")
+ }
+ if (!isObtained) {
+ throw UnsupportedOperationException(
+ "Message has not been obtained. Call order is incorrect."
+ )
+ }
+
+ if (message.level >= targetLogLevel) {
+ val strMessage = message.messagePrinter(message)
+ when (message.level) {
+ LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+ LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+ LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+ LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+ LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+ LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
+ }
+ }
+
+ isObtained = false
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 64ddbc7828ac..c961be946f79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -35,8 +35,10 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobile
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.function.Function
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
@@ -58,6 +60,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
private val testUtils = SceneTestUtils(this)
private val testScope = testUtils.testScope
+ private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -78,8 +81,10 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
underTest =
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
- getSecurityMode = getSecurityMode,
backgroundDispatcher = testUtils.testDispatcher,
+ flags = testUtils.sceneContainerFlags,
+ clock = clock,
+ getSecurityMode = getSecurityMode,
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
@@ -141,22 +146,6 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
}
@Test
- fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() =
- testScope.runTest {
- val authenticationChallengeResults by
- collectValues(underTest.authenticationChallengeResult)
-
- runCurrent()
- underTest.reportAuthenticationAttempt(true)
- runCurrent()
- underTest.reportAuthenticationAttempt(false)
- runCurrent()
- underTest.reportAuthenticationAttempt(true)
-
- assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true))
- }
-
- @Test
fun isPinEnhancedPrivacyEnabled() =
testScope.runTest {
whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id))
@@ -172,6 +161,45 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
assertThat(values.last()).isTrue()
}
+ @Test
+ fun lockoutEndTimestamp() =
+ testScope.runTest {
+ val lockoutEndMs = clock.elapsedRealtime() + 30.seconds.inWholeMilliseconds
+ whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[0].id))
+ .thenReturn(lockoutEndMs)
+ whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[1].id)).thenReturn(0)
+
+ // Switch to a user who is not locked-out.
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+
+ // Switch back to the locked-out user, verify the timestamp is up-to-date.
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(lockoutEndMs)
+
+ // After the lockout expires, null is returned.
+ clock.setElapsedRealtime(lockoutEndMs)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+ }
+
+ @Test
+ fun hasLockoutOccurred() =
+ testScope.runTest {
+ val hasLockoutOccurred by collectLastValue(underTest.hasLockoutOccurred)
+ assertThat(hasLockoutOccurred).isFalse()
+
+ underTest.reportLockoutStarted(1000)
+ assertThat(hasLockoutOccurred).isTrue()
+
+ clock.setElapsedRealtime(clock.elapsedRealtime() + 60.seconds.inWholeMilliseconds)
+
+ underTest.reportAuthenticationAttempt(isSuccessful = false)
+ assertThat(hasLockoutOccurred).isTrue()
+
+ underTest.reportAuthenticationAttempt(isSuccessful = true)
+ assertThat(hasLockoutOccurred).isFalse()
+ }
+
private fun setSecurityModeAndDispatchBroadcast(
securityMode: KeyguardSecurityModel.SecurityMode,
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 08cd7edba6af..c113b3744bc0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -21,14 +21,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -43,21 +47,23 @@ class AuthenticationInteractorTest : SysuiTestCase() {
private val testScope = utils.testScope
private val underTest = utils.authenticationInteractor()
+ private val onAuthenticationResult by
+ testScope.collectLastValue(underTest.onAuthenticationResult)
+ private val failedAuthenticationAttempts by
+ testScope.collectLastValue(underTest.failedAuthenticationAttempts)
+
@Test
fun authenticationMethod() =
testScope.runTest {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
- assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+ assertThat(authMethod).isEqualTo(Pin)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password)
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.Password)
+ assertThat(authMethod).isEqualTo(Password)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
}
@Test
@@ -66,51 +72,45 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ utils.authenticationRepository.setAuthenticationMethod(None)
- assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
- assertThat(underTest.getAuthenticationMethod())
- .isEqualTo(AuthenticationMethodModel.None)
+ assertThat(authMethod).isEqualTo(None)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
}
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
}
@Test
fun authenticate_withIncorrectPin_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
- .isEqualTo(AuthenticationResult.FAILED)
+ assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
}
@Test(expected = IllegalArgumentException::class)
fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
underTest.authenticate(listOf())
}
@Test
fun authenticate_withCorrectMaxLengthPin_succeeds() =
testScope.runTest {
- val pin = List(16) { 9 }
+ val correctMaxLengthPin = List(16) { 9 }
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
- overrideCredential(pin)
+ setAuthenticationMethod(Pin)
+ overrideCredential(correctMaxLengthPin)
}
- assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(correctMaxLengthPin))
}
@Test
@@ -122,88 +122,64 @@ class AuthenticationInteractorTest : SysuiTestCase() {
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(underTest.authenticate(List(17) { 9 }))
- .isEqualTo(AuthenticationResult.FAILED)
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+
+ assertFailed(underTest.authenticate(List(17) { 9 }))
}
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("password".toList()))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertSucceeded(underTest.authenticate("password".toList()))
}
@Test
fun authenticate_withIncorrectPassword_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("alohomora".toList()))
- .isEqualTo(AuthenticationResult.FAILED)
+ assertFailed(underTest.authenticate("alohomora".toList()))
}
@Test
fun authenticate_withCorrectPattern_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern
- )
+ utils.authenticationRepository.setAuthenticationMethod(Pattern)
- assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
}
@Test
fun authenticate_withIncorrectPattern_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pattern
- )
-
- assertThat(
- underTest.authenticate(
- listOf(
- AuthenticationPatternCoordinate(x = 2, y = 0),
- AuthenticationPatternCoordinate(x = 2, y = 1),
- AuthenticationPatternCoordinate(x = 2, y = 2),
- AuthenticationPatternCoordinate(x = 1, y = 2),
- )
- )
+ utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ val wrongPattern =
+ listOf(
+ AuthenticationPatternCoordinate(x = 2, y = 0),
+ AuthenticationPatternCoordinate(x = 2, y = 1),
+ AuthenticationPatternCoordinate(x = 2, y = 2),
+ AuthenticationPatternCoordinate(x = 1, y = 2),
)
- .isEqualTo(AuthenticationResult.FAILED)
+
+ assertFailed(underTest.authenticate(wrongPattern))
}
@Test
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
+ val shorterPin =
+ FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() }
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
- removeLast()
- },
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(lockout).isNull()
+ assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
+ assertThat(underTest.lockoutEndTimestamp).isNull()
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@@ -212,18 +188,17 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.FAILED)
+ val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+
+ assertFailed(
+ underTest.authenticate(wrongPin, tryAutoConfirm = true),
+ assertNoResultEvents = true,
+ )
}
@Test
@@ -231,18 +206,17 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.FAILED)
+ val longerPin = FakeAuthenticationRepository.DEFAULT_PIN + listOf(7)
+
+ assertFailed(
+ underTest.authenticate(longerPin, tryAutoConfirm = true),
+ assertNoResultEvents = true,
+ )
}
@Test
@@ -250,69 +224,54 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSucceeded(underTest.authenticate(correctPin, tryAutoConfirm = true))
}
@Test
fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
- setLockoutDuration(42)
+ reportLockoutStarted(42)
}
- val authResult =
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
- assertThat(authResult).isEqualTo(AuthenticationResult.SKIPPED)
+ assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
assertThat(isAutoConfirmEnabled).isFalse()
- assertThat(isUnlocked).isFalse()
assertThat(hintedPinLength).isNull()
+ assertThat(underTest.lockoutEndTimestamp).isNotNull()
}
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
testScope.runTest {
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
- assertThat(
- underTest.authenticate(
- FakeAuthenticationRepository.DEFAULT_PIN,
- tryAutoConfirm = true
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
+
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
}
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
+ utils.authenticationRepository.setAuthenticationMethod(Password)
- assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
- .isEqualTo(AuthenticationResult.SKIPPED)
+ assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
}
@Test
@@ -337,7 +296,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- val lockout by collectLastValue(underTest.lockout)
utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
@@ -345,92 +303,104 @@ class AuthenticationInteractorTest : SysuiTestCase() {
// Make many wrong attempts to trigger lockout.
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
}
- assertThat(lockout).isNotNull()
+ assertThat(underTest.lockoutEndTimestamp).isNotNull()
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
// Move the clock forward one more second, to completely finish the lockout period:
- advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
- assertThat(lockout).isNull()
+ advanceTimeBy(
+ FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds.plus(1.seconds)
+ )
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Auto-confirm is still disabled, because lockout occurred at least once in this
// session.
assertThat(isAutoConfirmEnabled).isFalse()
// Correct PIN and unlocks successfully, resetting the 'session'.
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
// Auto-confirm is re-enabled.
assertThat(isAutoConfirmEnabled).isTrue()
+ }
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ @Test
+ fun failedAuthenticationAttempts() =
+ testScope.runTest {
+ val failedAuthenticationAttempts by
+ collectLastValue(underTest.failedAuthenticationAttempts)
+
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
+
+ // Make many wrong attempts, leading to lockout:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { index ->
+ underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+ assertThat(failedAuthenticationAttempts).isEqualTo(index + 1)
+ }
+
+ // Correct PIN, but locked out, so doesn't attempt it:
+ assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+ assertThat(failedAuthenticationAttempts)
+ .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+ // Move the clock forward to finish the lockout period:
+ advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+ assertThat(failedAuthenticationAttempts)
+ .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+ // Correct PIN and no longer locked out so unlocks successfully:
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
}
@Test
- fun lockout() =
+ fun lockoutEndTimestamp() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
- assertThat(lockout).isNull()
+ utils.authenticationRepository.setAuthenticationMethod(Pin)
+ val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+ underTest.authenticate(correctPin)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Make many wrong attempts, but just shy of what's needed to get locked out:
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(lockout).isNull()
+ assertThat(underTest.lockoutEndTimestamp).isNull()
}
// Make one more wrong attempt, leading to lockout:
underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
+
+ val expectedLockoutEndTimestamp =
+ testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Correct PIN, but locked out, so doesn't attempt it:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
+ assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
// Move the clock forward to ALMOST skip the lockout, leaving one second to go:
- val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
- repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
- advanceTimeBy(1000)
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = lockoutTimeoutSec - (time + 1),
- )
- )
+ repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) {
+ advanceTimeBy(1.seconds)
+ assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
}
// Move the clock forward one more second, to completely finish the lockout period:
- advanceTimeBy(1000)
- assertThat(lockout).isNull()
+ advanceTimeBy(1.seconds)
+ assertThat(underTest.lockoutEndTimestamp).isNull()
// Correct PIN and no longer locked out so unlocks successfully:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
+ assertSucceeded(underTest.authenticate(correctPin))
+ assertThat(underTest.lockoutEndTimestamp).isNull()
}
@Test
@@ -438,7 +408,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -450,7 +420,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
overrideCredential(
buildList {
repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
@@ -467,7 +437,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
@@ -484,7 +454,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
utils.authenticationRepository.apply {
- setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ setAuthenticationMethod(Pin)
overrideCredential(
buildList {
repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
@@ -499,18 +469,45 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withTooShortPassword() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Password
- )
- assertThat(
- underTest.authenticate(
- buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
- add("$time")
- }
- }
- )
- )
- .isEqualTo(AuthenticationResult.SKIPPED)
+ utils.authenticationRepository.setAuthenticationMethod(Password)
+
+ val tooShortPassword = buildList {
+ repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ add("$time")
+ }
+ }
+ assertSkipped(underTest.authenticate(tooShortPassword))
+ }
+
+ private fun assertSucceeded(authenticationResult: AuthenticationResult) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(onAuthenticationResult).isTrue()
+ assertThat(underTest.lockoutEndTimestamp).isNull()
+ assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(failedAuthenticationAttempts).isEqualTo(0)
+ }
+
+ private fun assertFailed(
+ authenticationResult: AuthenticationResult,
+ assertNoResultEvents: Boolean = false,
+ ) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
+ if (assertNoResultEvents) {
+ assertThat(onAuthenticationResult).isNull()
+ } else {
+ assertThat(onAuthenticationResult).isFalse()
+ }
+ }
+
+ private fun assertSkipped(
+ authenticationResult: AuthenticationResult,
+ assertNoResultEvents: Boolean = true,
+ ) {
+ assertThat(authenticationResult).isEqualTo(AuthenticationResult.SKIPPED)
+ if (assertNoResultEvents) {
+ assertThat(onAuthenticationResult).isNull()
+ } else {
+ assertThat(onAuthenticationResult).isNotNull()
}
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index da97a1283261..1c1335f0db4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -923,14 +923,14 @@ public class AuthControllerTest extends SysuiTestCase {
doReturn(500).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
doReturn(1000).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
}
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 a726b7c2b075..b0beab932e21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -47,6 +47,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -54,7 +55,6 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -101,7 +101,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock
private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
- @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -117,6 +116,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock
private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -174,6 +174,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
{ defaultUdfpsTouchOverlayViewModel },
+ shadeInteractor
)
block()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index dddcf18c1ede..e5da1f86a841 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -90,11 +90,11 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -124,8 +124,6 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
-import javax.inject.Provider;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
@@ -208,6 +206,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
+ private ShadeInteractor mShadeInteractor;
+ @Mock
private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@Mock
private SessionTracker mSessionTracker;
@@ -216,8 +216,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@Mock
- private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@@ -333,13 +331,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
mActivityLaunchAnimator,
mBiometricExecutor,
mPrimaryBouncerInteractor,
+ mShadeInteractor,
mSinglePointerTouchProcessor,
mSessionTracker,
mAlternateBouncerInteractor,
mInputManager,
mock(KeyguardFaceAuthInteractor.class),
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels,
mSelectedUserInteractor,
mFpsUnlockTracker,
mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index ac16c135e6a1..13b53a896b70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -70,6 +71,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ protected @Mock ShadeInteractor mShadeInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
@@ -149,7 +151,8 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
- mKeyguardTransitionInteractor);
+ mKeyguardTransitionInteractor,
+ mShadeInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 9b1df7c0ffc0..99c18744f9b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -21,15 +21,15 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -79,7 +79,7 @@ class BouncerInteractorTest : SysuiTestCase() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
underTest.clearMessage()
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -149,7 +149,7 @@ class BouncerInteractorTest : SysuiTestCase() {
// Incomplete input.
assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
// Correct input.
assertThat(
@@ -159,7 +159,7 @@ class BouncerInteractorTest : SysuiTestCase() {
)
)
.isEqualTo(AuthenticationResult.SKIPPED)
- assertThat(message).isEmpty()
+ assertThat(message).isNull()
}
@Test
@@ -246,57 +246,40 @@ class BouncerInteractorTest : SysuiTestCase() {
}
@Test
- fun lockout() =
+ fun lockoutStarted() =
testScope.runTest {
- val lockout by collectLastValue(underTest.lockout)
+ val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
val message by collectLastValue(underTest.message)
+
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(lockout).isNull()
+ assertThat(lockoutStartedEvents).isEmpty()
+
+ // Try the wrong PIN repeatedly, until lockout is triggered:
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
// Wrong PIN.
assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
.isEqualTo(AuthenticationResult.FAILED)
if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+ assertThat(lockoutStartedEvents).isEmpty()
+ assertThat(message).isNotEmpty()
}
}
- assertThat(lockout)
- .isEqualTo(
- AuthenticationLockoutModel(
- failedAttemptCount =
- FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
- remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
- )
- )
- assertTryAgainMessage(
- message,
- FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
- )
-
- // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SKIPPED)
- assertTryAgainMessage(
- message,
- FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
- )
-
- lockout?.remainingSeconds?.let { seconds ->
- repeat(seconds) { time ->
- advanceTimeBy(1000)
- val remainingTimeSec = seconds - time - 1
- if (remainingTimeSec > 0) {
- assertTryAgainMessage(message, remainingTimeSec)
- }
- }
+ assertThat(authenticationInteractor.lockoutEndTimestamp).isNotNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(1)
+ assertThat(message).isNull()
+
+ // Advance the time to finish the lockout:
+ advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+ assertThat(authenticationInteractor.lockoutEndTimestamp).isNull()
+ assertThat(message).isNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(1)
+
+ // Trigger lockout again:
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+ // Wrong PIN.
+ underTest.authenticate(listOf(6, 7, 8, 9))
}
- assertThat(message).isEqualTo("")
- assertThat(lockout).isNull()
-
- // Correct PIN and no longer locked out so changes to the Gone scene:
- assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
- .isEqualTo(AuthenticationResult.SUCCEEDED)
- assertThat(lockout).isNull()
+ assertThat(lockoutStartedEvents.size).isEqualTo(2)
}
@Test
@@ -326,13 +309,6 @@ class BouncerInteractorTest : SysuiTestCase() {
verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
}
- private fun assertTryAgainMessage(
- message: String?,
- time: Int,
- ) {
- assertThat(message).isEqualTo("Try again in $time seconds.")
- }
-
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
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 16a935943dbf..4be9b0a49a69 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
@@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -135,19 +136,47 @@ class BouncerViewModelTest : SysuiTestCase() {
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(message?.isUpdateAnimated).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(message?.isUpdateAnimated).isFalse()
- lockout?.remainingSeconds?.let { remainingSeconds ->
- advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+ val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ advanceTimeBy(lockoutEndMs - testScope.currentTime)
+ assertThat(message?.isUpdateAnimated).isTrue()
+ }
+
+ @Test
+ fun lockoutMessage() =
+ testScope.runTest {
+ val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
+ val message by collectLastValue(underTest.message)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+ assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
+
+ repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
+ bouncerInteractor.authenticate(WRONG_PIN)
+ if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ assertThat(message?.text).isEqualTo(bouncerInteractor.message.value)
+ assertThat(message?.isUpdateAnimated).isTrue()
+ }
}
+ val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+ assertTryAgainMessage(message?.text, lockoutSeconds)
+ assertThat(message?.isUpdateAnimated).isFalse()
+
+ repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time ->
+ advanceTimeBy(1.seconds)
+ val remainingSeconds = lockoutSeconds - time - 1
+ if (remainingSeconds > 0) {
+ assertTryAgainMessage(message?.text, remainingSeconds)
+ }
+ }
+ assertThat(message?.text).isEmpty()
assertThat(message?.isUpdateAnimated).isTrue()
}
@@ -160,32 +189,30 @@ class BouncerViewModelTest : SysuiTestCase() {
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- val lockout by collectLastValue(bouncerInteractor.lockout)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(isInputEnabled).isFalse()
- lockout?.remainingSeconds?.let { remainingSeconds ->
- advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
- }
+ val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ advanceTimeBy(lockoutEndMs - testScope.currentTime)
assertThat(isInputEnabled).isTrue()
}
@Test
fun dialogMessage() =
testScope.runTest {
+ val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val dialogMessage by collectLastValue(underTest.dialogMessage)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- // Wrong PIN.
assertThat(dialogMessage).isNull()
- bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+ bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(dialogMessage).isNotEmpty()
@@ -241,4 +268,15 @@ class BouncerViewModelTest : SysuiTestCase() {
AuthenticationMethodModel.Sim,
)
}
+
+ private fun assertTryAgainMessage(
+ message: String?,
+ time: Int,
+ ) {
+ assertThat(message).isEqualTo("Try again in $time seconds.")
+ }
+
+ companion object {
+ private val WRONG_PIN = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 6d6baa57bb9d..64e6e5707d75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -28,6 +27,7 @@ import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -45,11 +45,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
- private val authenticationRepository = utils.authenticationRepository
- private val authenticationInteractor =
- utils.authenticationInteractor(
- repository = authenticationRepository,
- )
+ private val authenticationInteractor = utils.authenticationInteractor()
private val sceneInteractor = utils.sceneInteractor()
private val bouncerInteractor =
utils.bouncerInteractor(
@@ -61,12 +57,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
authenticationInteractor = authenticationInteractor,
actionButtonInteractor = utils.bouncerActionButtonInteractor(),
)
+ private val isInputEnabled = MutableStateFlow(true)
private val underTest =
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ isInputEnabled.asStateFlow(),
)
@Before
@@ -123,8 +120,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPasswordBouncer()
underTest.onPasswordInputChanged("password")
@@ -169,8 +165,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
lockDeviceAndOpenPasswordBouncer()
@@ -333,19 +328,15 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
) {
if (isLockedOut) {
repeat(failedAttemptCount) {
- authenticationRepository.reportAuthenticationAttempt(false)
+ utils.authenticationRepository.reportAuthenticationAttempt(false)
}
- val remainingTimeSeconds = 30
- authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
- authenticationRepository.lockout.value =
- AuthenticationLockoutModel(
- failedAttemptCount = failedAttemptCount,
- remainingSeconds = remainingTimeSeconds,
- )
+ utils.authenticationRepository.reportLockoutStarted(
+ 30.seconds.inWholeMilliseconds.toInt()
+ )
} else {
- authenticationRepository.reportAuthenticationAttempt(true)
- authenticationRepository.lockout.value = null
+ utils.authenticationRepository.reportAuthenticationAttempt(true)
}
+ isInputEnabled.value = !isLockedOut
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 8971423edd52..ed7609999804 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -111,8 +111,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
lockDeviceAndOpenPatternBouncer()
@@ -334,8 +333,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index c30e405ab911..db98d7632910 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -201,8 +201,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
@@ -236,8 +235,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
lockDeviceAndOpenPinBouncer()
@@ -265,8 +263,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
- val authResult by
- collectLastValue(authenticationInteractor.authenticationChallengeResult)
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 182712a13174..ddaa4889d6f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -208,11 +208,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
val repository = initCommunalWidgetRepository()
runCurrent()
- val ids = listOf(104, 103, 101)
- repository.updateWidgetOrder(ids)
+ val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ repository.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(ids)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index d3049d9080f3..565049baf6f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -99,7 +99,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
}
@Test
- fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() =
+ fun reportSuccessfulAuthentication_updatesIsUnlocked() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 910097eece52..ea19cb799b10 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -346,12 +347,14 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
}
@Test
- fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() =
+ fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ utils.deviceEntryRepository.setLockscreenEnabled(true)
assertThat(isUnlocked).isFalse()
- utils.authenticationRepository.reportAuthenticationAttempt(true)
+ authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
assertThat(isUnlocked).isTrue()
}
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 c110de969b57..224903ff36b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,8 +265,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
falsingCollector = utils.falsingCollector(),
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = utils.simBouncerInteractor,
- authenticationInteractor = utils.authenticationInteractor()
+ simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }
)
startable.start()
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 61d55f0ae667..2e4986dd1a10 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
@@ -90,8 +90,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = utils.simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
+ simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { authenticationInteractor },
)
@Test
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 1c5f221f2efb..4436be7cd7d7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -41,16 +41,13 @@ interface ClockProviderPlugin : Plugin, ClockProvider {
/** Interface for building clocks and providing information about those clocks */
interface ClockProvider {
+ /** Initializes the clock provider with debug log buffers */
+ fun initialize(buffers: ClockMessageBuffers?)
+
/** Returns metadata for all clocks this provider knows about */
fun getClocks(): List<ClockMetadata>
/** Initializes and returns the target clock design */
- @Deprecated("Use overload with ClockSettings")
- fun createClock(id: ClockId): ClockController {
- return createClock(ClockSettings(id, null))
- }
-
- /** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
/** A static thumbnail for rendering in some examples */
@@ -98,11 +95,20 @@ interface ClockFaceController {
/** Triggers for various animations */
val animations: ClockAnimations
-
- /** Some clocks may log debug information */
- var messageBuffer: MessageBuffer?
}
+/** For clocks that want to report debug information */
+data class ClockMessageBuffers(
+ /** Message buffer for general infra */
+ val infraMessageBuffer: MessageBuffer,
+
+ /** Message buffer for small clock renering */
+ val smallClockMessageBuffer: MessageBuffer,
+
+ /** Message buffer for large clock rendering */
+ val largeClockMessageBuffer: MessageBuffer,
+)
+
/** Specifies layout information for the */
interface ClockFaceLayout {
/** All clock views to add to the root constraint layout before applying constraints. */
diff --git a/packages/SystemUI/res/layout/widget_picker.xml b/packages/SystemUI/res/layout/widget_picker.xml
deleted file mode 100644
index 21dc224a6f14..000000000000
--- a/packages/SystemUI/res/layout/widget_picker.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<HorizontalScrollView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/widgets_container"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal">
- </LinearLayout>
-
-</HorizontalScrollView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0267454c9161..ee89edefcdbf 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -274,6 +274,10 @@
<!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
+ <!-- Starting translateY offset of the HUN appear and disappear animations. Indicates
+ the amount by the view is positioned above the screen before the animation starts. -->
+ <dimen name="heads_up_appear_y_above_screen">32dp</dimen>
+
<!-- padding between the heads up and the statusbar -->
<dimen name="heads_up_status_bar_padding">8dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6cb2d457daea..d511caba941b 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -262,4 +262,8 @@
<!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
<item type="id" name="alternate_bouncer_udfps_icon_view" />
+
+ <!-- Id for the udfps accessibility overlay -->
+ <item type="id" name="udfps_accessibility_overlay" />
+ <item type="id" name="udfps_accessibility_overlay_top_guideline" />
</resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 5b59e7da2487..2b4117866254 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,6 +34,9 @@ java_library {
srcs: [
":statslog-SystemUI-java-gen",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -70,6 +73,9 @@ android_library {
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=all"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -81,6 +87,9 @@ java_library {
static_kotlin_stdlib: false,
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -100,4 +109,7 @@ java_library {
},
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d8c1e41465db..131eb6b63df3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -367,7 +367,7 @@ public class QuickStepContract {
/**
* Corner radius that should be used on windows in order to cover the display.
* These values are expressed in pixels because they should not respect display or font
- * scaling, this means that we don't have to reload them on config changes.
+ * scaling. The corner radius may change when folding/unfolding the device.
*/
public static float getWindowCornerRadius(Context context) {
return ScreenDecorationsUtils.getWindowCornerRadius(context);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 76abad8ae863..bcc20448297d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -45,12 +45,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.LogLevel.DEBUG
-import com.android.systemui.log.dagger.KeyguardLargeClockLog
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.WeatherData
@@ -91,117 +90,120 @@ constructor(
private val context: Context,
@Main private val mainExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
- @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
- @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
+ private val clockBuffers: ClockMessageBuffers,
private val featureFlags: FeatureFlags,
private val zenModeController: ZenModeController,
) {
+ var loggers = listOf(
+ clockBuffers.infraMessageBuffer,
+ clockBuffers.smallClockMessageBuffer,
+ clockBuffers.largeClockMessageBuffer
+ ).map { Logger(it, TAG) }
+
var clock: ClockController? = null
+ get() = field
set(value) {
- smallClockOnAttachStateChangeListener?.let {
- field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
- smallClockFrame?.viewTreeObserver
- ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
- }
- largeClockOnAttachStateChangeListener?.let {
- field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
- }
-
+ disconnectClock(field)
field = value
- if (value != null) {
- smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.smallClock.messageBuffer = smallLogBuffer
- largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
- value.largeClock.messageBuffer = largeLogBuffer
-
- value.initialize(resources, dozeAmount, 0f)
-
- if (!regionSamplingEnabled) {
- updateColors()
- } else {
- clock?.let {
- smallRegionSampler = createRegionSampler(
- it.smallClock.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- isLockscreen = true,
- ::updateColors
- )?.apply { startRegionSampler() }
-
- largeRegionSampler = createRegionSampler(
- it.largeClock.view,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- isLockscreen = true,
- ::updateColors
- )?.apply { startRegionSampler() }
-
- updateColors()
- }
- }
- updateFontSizes()
- updateTimeListeners()
- weatherData?.let {
- if (WeatherData.DEBUG) {
- Log.i(TAG, "Pushing cached weather data to new clock: $it")
- }
- value.events.onWeatherDataChanged(it)
- }
- zenData?.let {
- value.events.onZenDataChanged(it)
- }
- alarmData?.let {
- value.events.onAlarmDataChanged(it)
- }
+ connectClock(value)
+ }
- smallClockOnAttachStateChangeListener =
- object : OnAttachStateChangeListener {
- var pastVisibility: Int? = null
- override fun onViewAttachedToWindow(view: View) {
- value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- // Match the asing for view.parent's layout classes.
- smallClockFrame = view.parent as ViewGroup
- smallClockFrame?.let { frame ->
- pastVisibility = frame.visibility
- onGlobalLayoutListener = OnGlobalLayoutListener {
- val currentVisibility = frame.visibility
- if (pastVisibility != currentVisibility) {
- pastVisibility = currentVisibility
- // when small clock is visible,
- // recalculate bounds and sample
- if (currentVisibility == View.VISIBLE) {
- smallRegionSampler?.stopRegionSampler()
- smallRegionSampler?.startRegionSampler()
- }
- }
- }
- frame.viewTreeObserver
- .addOnGlobalLayoutListener(onGlobalLayoutListener)
- }
- }
+ private fun disconnectClock(clock: ClockController?) {
+ if (clock == null) { return; }
+ smallClockOnAttachStateChangeListener?.let {
+ clock.smallClock.view.removeOnAttachStateChangeListener(it)
+ smallClockFrame?.viewTreeObserver
+ ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+ }
+ largeClockOnAttachStateChangeListener?.let {
+ clock.largeClock.view.removeOnAttachStateChangeListener(it)
+ }
+ }
- override fun onViewDetachedFromWindow(p0: View) {
- smallClockFrame?.viewTreeObserver
- ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
- }
- }
- value.smallClock.view
- .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+ private fun connectClock(clock: ClockController?) {
+ if (clock == null) { return; }
+ val clockStr = clock.toString()
+ loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
- largeClockOnAttachStateChangeListener =
- object : OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(p0: View) {
- value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
- override fun onViewDetachedFromWindow(p0: View) {
+ clock.initialize(resources, dozeAmount, 0f)
+
+ if (!regionSamplingEnabled) {
+ updateColors()
+ } else {
+ smallRegionSampler = createRegionSampler(
+ clock.smallClock.view,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ isLockscreen = true,
+ ::updateColors
+ ).apply { startRegionSampler() }
+
+ largeRegionSampler = createRegionSampler(
+ clock.largeClock.view,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ isLockscreen = true,
+ ::updateColors
+ ).apply { startRegionSampler() }
+
+ updateColors()
+ }
+ updateFontSizes()
+ updateTimeListeners()
+
+ weatherData?.let {
+ if (WeatherData.DEBUG) {
+ Log.i(TAG, "Pushing cached weather data to new clock: $it")
+ }
+ clock.events.onWeatherDataChanged(it)
+ }
+ zenData?.let {
+ clock.events.onZenDataChanged(it)
+ }
+ alarmData?.let {
+ clock.events.onAlarmDataChanged(it)
+ }
+
+ smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+ var pastVisibility: Int? = null
+ override fun onViewAttachedToWindow(view: View) {
+ clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ // Match the asing for view.parent's layout classes.
+ smallClockFrame = (view.parent as ViewGroup)?.also { frame ->
+ pastVisibility = frame.visibility
+ onGlobalLayoutListener = OnGlobalLayoutListener {
+ val currentVisibility = frame.visibility
+ if (pastVisibility != currentVisibility) {
+ pastVisibility = currentVisibility
+ // when small clock is visible,
+ // recalculate bounds and sample
+ if (currentVisibility == View.VISIBLE) {
+ smallRegionSampler?.stopRegionSampler()
+ smallRegionSampler?.startRegionSampler()
+ }
}
+ }
+ frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
}
- value.largeClock.view
- .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+ }
+
+ override fun onViewDetachedFromWindow(p0: View) {
+ smallClockFrame?.viewTreeObserver
+ ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
}
+ clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+ largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View) {
+ clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+ override fun onViewDetachedFromWindow(p0: View) {}
+ }
+ clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+ }
@VisibleForTesting
var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
@@ -247,6 +249,7 @@ constructor(
largeClock.events.onRegionDarknessChanged(isRegionDark)
}
}
+
protected open fun createRegionSampler(
sampledView: View,
mainExecutor: Executor?,
@@ -254,7 +257,7 @@ constructor(
regionSamplingEnabled: Boolean,
isLockscreen: Boolean,
updateColors: () -> Unit
- ): RegionSampler? {
+ ): RegionSampler {
return RegionSampler(
sampledView,
mainExecutor,
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 661ce2ce60ba..878a5d88f164 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -28,9 +28,8 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
@@ -56,7 +55,7 @@ public abstract class ClockRegistryModule {
FeatureFlags featureFlags,
@Main Resources resources,
LayoutInflater layoutInflater,
- @KeyguardClockLog LogBuffer logBuffer) {
+ ClockMessageBuffers clockBuffers) {
ClockRegistry registry = new ClockRegistry(
context,
pluginManager,
@@ -72,7 +71,7 @@ public abstract class ClockRegistryModule {
featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
migrateClocksToBlueprint()),
context.getString(R.string.lockscreen_clock_id_fallback),
- logBuffer,
+ clockBuffers,
/* keepAllLoaded = */ false,
/* subTag = */ "System",
/* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index c07a4d26f476..7295936ddc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,8 +16,6 @@
package com.android.systemui;
-import android.content.res.Configuration;
-
import androidx.annotation.NonNull;
import java.io.PrintWriter;
@@ -42,13 +40,6 @@ public interface CoreStartable extends Dumpable {
/** Main entry point for implementations. Called shortly after SysUI startup. */
void start();
- /** Called when the device configuration changes. This will not be called before
- * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
- *
- * @see android.app.Application#onConfigurationChanged(Configuration) */
- default void onConfigurationChanged(Configuration newConfig) {
- }
-
@Override
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 008de4387c4a..e03c62783475 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@ import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.settings.SecureSettings;
@@ -109,7 +110,8 @@ import javax.inject.Inject;
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations implements CoreStartable, Dumpable {
+public class ScreenDecorations implements
+ CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
private static final boolean DEBUG_LOGGING = false;
private static final String TAG = "ScreenDecorations";
@@ -575,7 +577,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
if (mPendingManualConfigUpdate) {
mPendingManualConfigUpdate = false;
- onConfigurationChanged(mContext.getResources().getConfiguration());
+ onConfigChanged(mContext.getResources().getConfiguration());
}
}
}
@@ -1062,7 +1064,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
Log.i(TAG, "ScreenDecorations is disabled");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
new file mode 100644
index 000000000000..044312bcfe14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ScreenDecorationsModule {
+ /** Start ScreenDecorations. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenDecorations::class)
+ fun bindScreenDecorationsCoreStartable(impl: ScreenDecorations): CoreStartable
+
+ /** Listen to config changes for ScreenDecorations. */
+ @Binds
+ @IntoSet
+ fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c3f64803758b..01f6971de373 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,11 +37,11 @@ import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.NotificationChannels;
@@ -354,19 +354,6 @@ public class SystemUIApplication extends Application implements
}
configController.onConfigurationChanged(newConfig);
Trace.endSection();
- int len = mServices.length;
- for (int i = 0; i < len; i++) {
- if (mServices[i] != null) {
- if (Trace.isEnabled()) {
- Trace.traceBegin(
- Trace.TRACE_TAG_APP,
- mServices[i].getClass().getSimpleName()
- + ".onConfigurationChanged()");
- }
- mServices[i].onConfigurationChanged(newConfig);
- Trace.endSection();
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 3cb6314639e8..3ca95e11d789 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -77,7 +77,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@VisibleForTesting
SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
- private static class ControllerSupplier extends
+ private static class WindowMagnificationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
private final Context mContext;
@@ -86,7 +86,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private final SysUiState mSysUiState;
private final SecureSettings mSecureSettings;
- ControllerSupplier(Context context, Handler handler,
+ WindowMagnificationControllerSupplier(Context context, Handler handler,
WindowMagnifierCallback windowMagnifierCallback,
DisplayManager displayManager, SysUiState sysUiState,
SecureSettings secureSettings) {
@@ -118,7 +118,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
}
@VisibleForTesting
- DisplayIdIndexSupplier<WindowMagnificationController> mMagnificationControllerSupplier;
+ DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
private static class SettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
@@ -168,7 +168,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
mOverviewProxyService = overviewProxyService;
mDisplayTracker = displayTracker;
mA11yLogger = a11yLogger;
- mMagnificationControllerSupplier = new ControllerSupplier(context,
+ mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
@@ -196,7 +196,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private void updateSysUiStateFlag() {
//TODO(b/187510533): support multi-display once SysuiState supports it.
final WindowMagnificationController controller =
- mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId());
+ mWindowMagnificationControllerSupplier.valueAt(
+ mDisplayTracker.getDefaultDisplayId());
if (controller != null) {
controller.updateSysUIStateFlag();
} else {
@@ -212,7 +213,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
@@ -222,7 +223,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
void setScaleForWindowMagnification(int displayId, float scale) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.setScale(scale);
}
@@ -231,7 +232,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
final WindowMagnificationController windowMagnificationcontroller =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationcontroller != null) {
windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
}
@@ -241,7 +242,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
callback);
@@ -252,7 +253,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
void disableWindowMagnification(int displayId,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.deleteWindowMagnification(callback);
}
@@ -417,7 +418,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSetMagnifierSizeInternal(int displayId, int index) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.changeMagnificationSize(index);
}
@@ -426,7 +427,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.setDiagonalScrolling(enable);
}
@@ -435,7 +436,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
windowMagnificationController.setEditMagnifierSizeMode(enable);
}
@@ -444,7 +445,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onModeSwitchInternal(int displayId, int newMode) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
@@ -463,7 +464,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
if (isWindowMagnifierActivated) {
@@ -495,7 +496,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println(TAG);
- mMagnificationControllerSupplier.forEach(
+ mWindowMagnificationControllerSupplier.forEach(
magnificationController -> magnificationController.dump(pw));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7a8161eb6c3d..da49201db808 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import android.accessibilityservice.AccessibilityService;
@@ -57,6 +58,7 @@ import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
@@ -71,7 +73,7 @@ import javax.inject.Inject;
* Class to register system actions with accessibility framework.
*/
@SysUISingleton
-public class SystemActions implements CoreStartable {
+public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "SystemActions";
/**
@@ -234,7 +236,7 @@ public class SystemActions implements CoreStartable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
if (!locale.equals(mLocale)) {
mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
new file mode 100644
index 000000000000..4d6d784fac2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface SystemActionsModule {
+ /** Start SystemActions. */
+ @Binds
+ @IntoMap
+ @ClassKey(SystemActions::class)
+ fun bindSystemActionsStartable(sysui: SystemActions): CoreStartable
+
+ /** Listen to config changes for SystemActions. */
+ @Binds @IntoSet fun bindSystemActionsConfigChanges(sysui: SystemActions): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0bd4859eefd5..dde9f48424ea 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1303,7 +1303,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
} else if (id == R.id.close_button) {
setEditMagnifierSizeMode(false);
} else {
- animateBounceEffect();
+ animateBounceEffectIfNeeded();
}
}
@@ -1465,7 +1465,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mBounceEffectDuration = duration;
}
- private void animateBounceEffect() {
+ private void animateBounceEffectIfNeeded() {
+ if (mMirrorView == null) {
+ // run the animation only if the mirror view is not null
+ return;
+ }
+
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127a4e4b..49e0df6f6afc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController {
+class DragToInteractAnimationController {
private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@ class DismissAnimationController {
private float mMinDismissSize;
private float mSizePercent;
- DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
mDismissView = dismissView;
mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@ class DismissAnimationController {
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@ class DismissAnimationController {
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeUpMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cecc47b0..a2705584d76a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@ class MenuAnimationController {
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DismissAnimationController.DismissCallback mDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@ class MenuAnimationController {
}
void setDismissCallback(
- DismissAnimationController.DismissCallback dismissCallback) {
+ DragToInteractAnimationController.DismissCallback dismissCallback) {
mDismissCallback = dismissCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f10253..52e7b91d373e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private Optional<Runnable> mOnActionDownEnd = Optional.empty();
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
- DismissAnimationController dismissAnimationController) {
+ DragToInteractAnimationController dragToInteractAnimationController) {
mMenuAnimationController = menuAnimationController;
- mDismissAnimationController = dismissAnimationController;
+ mDragToInteractAnimationController = dragToInteractAnimationController;
}
@Override
@@ -67,7 +67,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
- mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+ mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
mOnActionDownEnd.ifPresent(Runnable::run);
break;
@@ -78,9 +78,10 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mDismissAnimationController.showDismissView(/* show= */ true);
+ mDragToInteractAnimationController.showDismissView(/* show= */ true);
- if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+ motionEvent)) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mIsDragging = false;
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
return true;
}
- if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+ motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index ff3a9e3bd409..62d5feb7d024 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@ class MenuViewLayer extends FrameLayout implements
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mDockTooltipObserver =
this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@ class MenuViewLayer extends FrameLayout implements
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
- mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
hideMenuAndShowMessage();
mDismissView.hide();
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
});
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
mMenuView.setMoveToTuckedListener(this);
@@ -243,7 +244,7 @@ class MenuViewLayer extends FrameLayout implements
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
- mDismissAnimationController.updateResources();
+ mDragToInteractAnimationController.updateResources();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index fda23b7f2a9c..f2b55f456f00 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,13 +24,13 @@ import android.os.UserHandle
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -43,9 +43,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -60,12 +58,6 @@ import kotlinx.coroutines.withContext
/** Defines interface for classes that can access authentication-related application state. */
interface AuthenticationRepository {
/**
- * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
- * in order to unlock the device.
- */
- val authenticationChallengeResult: SharedFlow<Boolean>
-
- /**
* The exact length a PIN should be for us to enable PIN length hinting.
*
* A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
@@ -80,16 +72,6 @@ interface AuthenticationRepository {
val isPatternVisible: StateFlow<Boolean>
/**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates throttling isn't
- * active.
- */
- val lockout: MutableStateFlow<AuthenticationLockoutModel?>
-
- /** Whether throttling has occurred at least once since the last successful authentication. */
- val hasLockoutOccurred: MutableStateFlow<Boolean>
-
- /**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
* Note that the length of the PIN is also important to take into consideration, please see
@@ -98,6 +80,28 @@ interface AuthenticationRepository {
val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
/**
+ * The number of failed authentication attempts for the selected user since their last
+ * successful authentication.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int>
+
+ /**
+ * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+ * attempt authentication again. Returns `null` if no lockout is active.
+ *
+ * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+ *
+ * Also note that the value may change when the selected user is changed.
+ */
+ val lockoutEndTimestamp: Long?
+
+ /**
+ * Whether lockout has occurred at least once since the last successful authentication of any
+ * user.
+ */
+ val hasLockoutOccurred: StateFlow<Boolean>
+
+ /**
* The currently-configured authentication method. This determines how the authentication
* challenge needs to be completed in order to unlock an otherwise locked device.
*
@@ -142,23 +146,6 @@ interface AuthenticationRepository {
/** Reports that the user has entered a temporary device lockout (throttling). */
suspend fun reportLockoutStarted(durationMs: Int)
- /** Returns the current number of failed authentication attempts. */
- suspend fun getFailedAuthenticationAttemptCount(): Int
-
- /**
- * Returns the timestamp for when the current lockout will end, allowing the user to attempt
- * authentication again.
- *
- * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
- */
- suspend fun getLockoutEndTimestamp(): Long
-
- /**
- * Sets the lockout timeout duration (time during which the user should not be allowed to
- * attempt authentication).
- */
- suspend fun setLockoutDuration(durationMs: Int)
-
/**
* Checks the given [LockscreenCredential] to see if it's correct, returning an
* [AuthenticationResultModel] representing what happened.
@@ -172,6 +159,8 @@ class AuthenticationRepositoryImpl
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ flags: SceneContainerFlags,
+ private val clock: SystemClock,
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
@@ -179,8 +168,6 @@ constructor(
mobileConnectionsRepository: MobileConnectionsRepository,
) : AuthenticationRepository {
- override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
override val hintedPinLength: Int = 6
override val isPatternVisible: StateFlow<Boolean> =
@@ -189,10 +176,6 @@ constructor(
getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
)
- override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
- override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = false,
@@ -234,6 +217,31 @@ constructor(
getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) },
)
+ private val _failedAuthenticationAttempts = MutableStateFlow(0)
+ override val failedAuthenticationAttempts: StateFlow<Int> =
+ _failedAuthenticationAttempts.asStateFlow()
+
+ override val lockoutEndTimestamp: Long?
+ get() =
+ lockPatternUtils.getLockoutAttemptDeadline(selectedUserId).takeIf {
+ clock.elapsedRealtime() < it
+ }
+
+ private val _hasLockoutOccurred = MutableStateFlow(false)
+ override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
+
+ init {
+ if (flags.isEnabled()) {
+ // Hydrate failedAuthenticationAttempts initially and whenever the selected user
+ // changes.
+ applicationScope.launch {
+ userRepository.selectedUserInfo.collect {
+ _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
+ }
+ }
+ }
+ }
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return withContext(backgroundDispatcher) {
blockingAuthenticationMethodInternal(selectedUserId)
@@ -248,35 +256,20 @@ constructor(
withContext(backgroundDispatcher) {
if (isSuccessful) {
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+ _hasLockoutOccurred.value = false
} else {
lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
}
- authenticationChallengeResult.emit(isSuccessful)
+ _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
}
}
override suspend fun reportLockoutStarted(durationMs: Int) {
- return withContext(backgroundDispatcher) {
- lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
- }
- }
-
- override suspend fun getFailedAuthenticationAttemptCount(): Int {
- return withContext(backgroundDispatcher) {
- lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
- }
- }
-
- override suspend fun getLockoutEndTimestamp(): Long {
- return withContext(backgroundDispatcher) {
- lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
- }
- }
-
- override suspend fun setLockoutDuration(durationMs: Int) {
+ lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
withContext(backgroundDispatcher) {
- lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
+ lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
}
+ _hasLockoutOccurred.value = true
}
override suspend fun checkCredential(
@@ -292,6 +285,12 @@ constructor(
}
}
+ private suspend fun getFailedAuthenticationAttemptCount(): Int {
+ return withContext(backgroundDispatcher) {
+ lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+ }
+ }
+
private val selectedUserId: Int
get() = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 797154e85082..c85ffe6ca56f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,36 +16,25 @@
package com.android.systemui.authentication.domain.interactor
-import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
-import kotlin.math.ceil
-import kotlin.math.max
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/**
* Hosts application business logic related to user authentication.
@@ -59,10 +48,7 @@ class AuthenticationInteractor
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: AuthenticationRepository,
- private val userRepository: UserRepository,
- private val clock: SystemClock,
) {
/**
* The currently-configured authentication method. This determines how the authentication
@@ -85,13 +71,6 @@ constructor(
val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
/**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates lockout isn't
- * active.
- */
- val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
-
- /**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
* Note that the length of the PIN is also important to take into consideration, please see
@@ -130,26 +109,35 @@ constructor(
/** Whether the pattern should be visible for the currently-selected user. */
val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+ private val _onAuthenticationResult = MutableSharedFlow<Boolean>()
/**
* Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security
* challenge is attempted by the user in order to unlock the device.
*/
- val authenticationChallengeResult: SharedFlow<Boolean> =
- repository.authenticationChallengeResult
+ val onAuthenticationResult: SharedFlow<Boolean> = _onAuthenticationResult.asSharedFlow()
/** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
- private var lockoutCountdownJob: Job? = null
+ /**
+ * The number of failed authentication attempts for the selected user since the last successful
+ * authentication.
+ */
+ val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
- init {
- applicationScope.launch {
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
- .collect { onSelectedUserChanged() }
- }
- }
+ /**
+ * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+ * attempt authentication again. Returns `null` if no lockout is active.
+ *
+ * To be notified whenever a lockout is started, the caller should subscribe to
+ * [onAuthenticationResult].
+ *
+ * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+ *
+ * Also note that the value may change when the selected user is changed.
+ */
+ val lockoutEndTimestamp: Long?
+ get() = repository.lockoutEndTimestamp
/**
* Returns the currently-configured authentication method. This determines how the
@@ -190,7 +178,7 @@ constructor(
val skipCheck =
when {
// Lockout is active, the UI layer should not have called this; skip the attempt.
- lockout.value != null -> true
+ repository.lockoutEndTimestamp != null -> true
// The input is too short; skip the attempt.
input.isTooShort(authMethod) -> true
// Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -211,27 +199,16 @@ constructor(
credential.zeroize()
if (authenticationResult.isSuccessful || !tryAutoConfirm) {
- repository.reportAuthenticationAttempt(
- isSuccessful = authenticationResult.isSuccessful,
- )
+ repository.reportAuthenticationAttempt(authenticationResult.isSuccessful)
}
// Check if lockout should start and, if so, kick off the countdown:
if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
- repository.apply {
- setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
- reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
- hasLockoutOccurred.value = true
- }
- startLockoutCountdown()
+ repository.reportLockoutStarted(authenticationResult.lockoutDurationMs)
}
- if (authenticationResult.isSuccessful) {
- // Since authentication succeeded, refresh lockout to make sure the state is completely
- // reflecting the upstream source of truth.
- refreshLockout()
-
- repository.hasLockoutOccurred.value = false
+ if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+ _onAuthenticationResult.emit(authenticationResult.isSuccessful)
}
return if (authenticationResult.isSuccessful) {
@@ -249,54 +226,6 @@ constructor(
}
}
- /** Starts refreshing the lockout state every second. */
- private suspend fun startLockoutCountdown() {
- cancelLockoutCountdown()
- lockoutCountdownJob =
- applicationScope.launch {
- while (refreshLockout()) {
- delay(1.seconds.inWholeMilliseconds)
- }
- }
- }
-
- /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
- private fun cancelLockoutCountdown() {
- lockoutCountdownJob?.cancel()
- lockoutCountdownJob = null
- }
-
- /** Notifies that the currently-selected user has changed. */
- private suspend fun onSelectedUserChanged() {
- cancelLockoutCountdown()
- if (refreshLockout()) {
- startLockoutCountdown()
- }
- }
-
- /**
- * Refreshes the lockout state, hydrating the repository with the latest state.
- *
- * @return Whether lockout is active or not.
- */
- private suspend fun refreshLockout(): Boolean {
- withContext("$TAG#refreshLockout", backgroundDispatcher) {
- val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
- val deadline = async { repository.getLockoutEndTimestamp() }
- val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
- repository.lockout.value =
- if (remainingMs > 0) {
- AuthenticationLockoutModel(
- failedAttemptCount = failedAttemptCount.await(),
- remainingSeconds = ceil(remainingMs / 1000f).toInt(),
- )
- } else {
- null // Lockout ended.
- }
- }
- return repository.lockout.value != null
- }
-
private fun AuthenticationMethodModel.createCredential(
input: List<Any>
): LockscreenCredential? {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564a1df4..83d415fa936e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -403,6 +403,13 @@ public class AuthContainerView extends LinearLayout
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined
+ * that accounts for iconView size, to prevent prompt resizing being visible to the
+ * user.
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ view.setVisibility(View.INVISIBLE);
mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
// TODO(b/201510778): This uses the wrong timeout in some cases
getJankListener(view, TRANSIT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5fba761b2f09..8a1a2da6cf3f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -84,6 +84,7 @@ import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -114,8 +115,12 @@ import kotlinx.coroutines.CoroutineScope;
* {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
- AuthDialogCallback, DozeReceiver {
+public class AuthController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks,
+ AuthDialogCallback,
+ DozeReceiver {
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
@@ -1297,7 +1302,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
updateSensorLocations();
// TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 66fb8ca2832d..7d9ec08bcb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,16 +23,14 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.ViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.PrintWriter
@@ -49,7 +47,7 @@ import java.io.PrintWriter
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ protected val shadeInteractor: ShadeInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -94,20 +92,18 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
// can make the view not visible; and we still want to listen for events
// that may make the view visible again.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- listenForBouncerExpansion(this)
+ listenForShadeExpansion(this)
}
}
}
@VisibleForTesting
- open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
return scope.launch {
- primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
- if (statusBarStateController.state != SHADE) {
- notificationShadeVisible = expansion > 0f
- view.onExpansionChanged(expansion)
- updatePauseAuth()
- }
+ shadeInteractor.anyExpansion.collect { expansion ->
+ notificationShadeVisible = expansion > 0f
+ view.onExpansionChanged(expansion)
+ updatePauseAuth()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 03749a9dc0df..e7b0d9fd9d85 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -26,13 +26,13 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 65668b56a9f3..2fd13b349438 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -86,11 +86,10 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -115,7 +114,6 @@ import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import javax.inject.Provider;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
@@ -152,7 +150,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
- @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@@ -166,6 +163,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @NonNull private final ShadeInteractor mShadeInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
@@ -294,7 +292,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
- mDefaultUdfpsTouchOverlayViewModel
+ mDefaultUdfpsTouchOverlayViewModel,
+ mShadeInteractor
)));
}
@@ -623,7 +622,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
} else {
onKeyguard = mOverlay != null
&& mOverlay.getAnimationViewController()
- instanceof UdfpsKeyguardViewControllerAdapter;
+ instanceof UdfpsKeyguardViewControllerLegacy;
}
return onKeyguard
&& mKeyguardStateController.canDismissLockScreen()
@@ -660,13 +659,13 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+ @NonNull ShadeInteractor shadeInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull InputManager inputManager,
@NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
- @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull FpsUnlockTracker fpsUnlockTracker,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -710,6 +709,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
@@ -737,7 +737,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
return Unit.INSTANCE;
});
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
- mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index dae6d08f7331..b94a1779e10b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -55,9 +55,9 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -108,6 +108,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
) {
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -278,7 +279,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -304,6 +305,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
udfpsKeyguardAccessibilityDelegate,
selectedUserInteractor,
transitionInteractor,
+ shadeInteractor,
)
}
REASON_AUTH_BP -> {
@@ -311,7 +313,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -321,7 +323,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -438,7 +440,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
if (DeviceEntryUdfpsRefactor.isEnabled) {
!keyguardStateController.isShowing
} else {
- animation !is UdfpsKeyguardViewControllerAdapter
+ animation !is UdfpsKeyguardViewControllerLegacy
}
if (keyguardNotShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index 88002e7b8492..ab3fbb191527 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -28,13 +28,13 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
class UdfpsFpmEmptyViewController(
view: UdfpsFpmEmptyView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 109741c093dd..9f170241269d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -33,10 +33,10 @@ import com.android.systemui.dump.DumpManager
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.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -69,20 +69,20 @@ open class UdfpsKeyguardViewControllerLegacy(
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val selectedUserInteractor: SelectedUserInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
+ shadeInteractor: ShadeInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
+ ) {
private val uniqueIdentifier = this.toString()
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -321,7 +321,7 @@ open class UdfpsKeyguardViewControllerLegacy(
}
@VisibleForTesting
- override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 8ae6f87f4f83..307b9856cbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics.dagger
import android.content.res.Resources
import com.android.internal.R
import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
@@ -38,18 +39,30 @@ import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
import com.android.systemui.biometrics.udfps.OverlapDetector
import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.util.concurrent.Executor
import javax.inject.Qualifier
/** Dagger module for all things biometric. */
@Module
interface BiometricsModule {
+ /** Starts AuthController. */
+ @Binds
+ @IntoMap
+ @ClassKey(AuthController::class)
+ fun bindAuthControllerStartable(service: AuthController): CoreStartable
+
+ /** Listen to config changes for AuthController. */
+ @Binds
+ @IntoSet
+ fun bindAuthControllerConfigChanges(service: AuthController): ConfigurationListener
@Binds
@IntoMap
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 90e4a3821634..a7fb6f72a41e 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
@@ -97,7 +97,13 @@ object BiometricViewBinder {
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
-
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is determined that
+ * accounts for iconView size, to prevent prompt resizing being visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove this once constraint layout is implemented
+ */
+ iconView.addLottieOnCompositionLoadedListener { viewModel.setIsIconViewLoaded(true) }
PromptIconViewBinder.bind(
iconView,
iconOverlayView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 7e16d1e1d668..f340bd81f951 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -30,7 +30,6 @@ import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
-import com.android.systemui.res.R
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -41,6 +40,8 @@ import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** Helper for [BiometricViewBinder] to handle resize transitions. */
@@ -92,8 +93,22 @@ object BiometricViewSizeBinder {
// TODO(b/251476085): migrate the legacy panel controller and simplify this
view.repeatWhenAttached {
var currentSize: PromptSize? = null
+
lifecycleScope.launch {
- viewModel.size.collect { size ->
+ /**
+ * View is only set visible in BiometricViewSizeBinder once PromptSize is
+ * determined that accounts for iconView size, to prevent prompt resizing being
+ * visible to the user.
+ *
+ * TODO(b/288175072): May be able to remove isIconViewLoaded once constraint
+ * layout is implemented
+ */
+ combine(viewModel.isIconViewLoaded, viewModel.size, ::Pair).collect {
+ (isIconViewLoaded, size) ->
+ if (!isIconViewLoaded) {
+ return@collect
+ }
+
// prepare for animated size transitions
for (v in viewsToHideWhenSmall) {
v.showTextOrHide(forceHide = size.isSmall)
@@ -196,8 +211,9 @@ object BiometricViewSizeBinder {
}
}
}
-
currentSize = size
+ view.visibility = View.VISIBLE
+ viewModel.setIsIconViewLoaded(false)
notifyAccessibilityChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446fd689..c36e0e21d021 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@ class SideFpsOverlayViewBinder
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
- private val biometricStatusInteractor: BiometricStatusInteractor,
- private val displayStateInteractor: DisplayStateInteractor,
- private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
- private val fpsUnlockTracker: FpsUnlockTracker,
- private val layoutInflater: LayoutInflater,
- private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
- private val sfpsSensorInteractor: SideFpsSensorInteractor,
- private val windowManager: WindowManager
+ private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+ private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+ private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+ private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+ private val layoutInflater: Lazy<LayoutInflater>,
+ private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+ private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+ private val windowManager: Lazy<WindowManager>
) : CoreStartable {
override fun start() {
if (!SideFpsControllerRefactor.isEnabled) {
return
}
+
applicationScope
.launch {
- combine(
- biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
- sideFpsProgressBarViewModel.isVisible,
- ::Triple
- )
- .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
- .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
- val (
- systemServerAuthReason,
- showIndicatorForDeviceEntry,
- progressBarIsVisible) =
- combinedFlows
- if (!isInRearDisplayMode) {
- if (progressBarIsVisible) {
- hide()
- } else if (systemServerAuthReason != NotRunning) {
- show()
- } else if (showIndicatorForDeviceEntry) {
- show()
- } else {
- hide()
+ sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+ if (isSfpsAvailable) {
+ combine(
+ biometricStatusInteractor.get().sfpsAuthenticationReason,
+ deviceEntrySideFpsOverlayInteractor
+ .get()
+ .showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.get().isVisible,
+ ::Triple
+ )
+ .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+ .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+ val (
+ systemServerAuthReason,
+ showIndicatorForDeviceEntry,
+ progressBarIsVisible) =
+ combinedFlows
+ if (!isInRearDisplayMode) {
+ if (progressBarIsVisible) {
+ hide()
+ } else if (systemServerAuthReason != NotRunning) {
+ show()
+ } else if (showIndicatorForDeviceEntry) {
+ show()
+ } else {
+ hide()
+ }
+ }
}
- }
}
+ }
}
- .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+ .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
}
private var overlayView: View? = null
@@ -113,29 +121,29 @@ constructor(
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
- windowManager.removeView(it)
+ windowManager.get().removeView(it)
}
}
- overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
- biometricStatusInteractor,
- deviceEntrySideFpsOverlayInteractor,
- displayStateInteractor,
- sfpsSensorInteractor,
- sideFpsProgressBarViewModel
+ biometricStatusInteractor.get(),
+ deviceEntrySideFpsOverlayInteractor.get(),
+ displayStateInteractor.get(),
+ sfpsSensorInteractor.get(),
+ sideFpsProgressBarViewModel.get()
)
- bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+ bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
overlayView!!.visibility = View.INVISIBLE
- windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+ windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
- windowManager.removeView(overlayView)
+ windowManager.get().removeView(overlayView)
overlayView = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
deleted file mode 100644
index 6f4e1a3cae46..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui.controller
-
-import com.android.systemui.biometrics.UdfpsAnimationViewController
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/** Class that coordinates non-HBM animations during keyguard authentication. */
-@ExperimentalCoroutinesApi
-open class UdfpsKeyguardViewController(
- val view: UdfpsKeyguardView,
- statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
- systemUIDialogManager: SystemUIDialogManager,
- dumpManager: DumpManager,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
-) :
- UdfpsAnimationViewController<UdfpsKeyguardView>(
- view,
- statusBarStateController,
- primaryBouncerInteractor,
- systemUIDialogManager,
- dumpManager,
- ),
- UdfpsKeyguardViewControllerAdapter {
- private val uniqueIdentifier = this.toString()
- override val tag: String
- get() = TAG
-
- init {
- udfpsKeyguardViewModels.bindViews(view)
- }
-
- public override fun onViewAttached() {
- super.onViewAttached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
- }
-
- public override fun onViewDetached() {
- super.onViewDetached()
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
- }
-
- override fun shouldPauseAuth(): Boolean {
- return !view.isVisible()
- }
-
- override fun listenForTouchesOutsideView(): Boolean {
- return true
- }
-
- companion object {
- private const val TAG = "UdfpsKeyguardViewController"
- }
-}
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 6d0a58e202bd..d899827ebb2e 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
@@ -192,6 +192,28 @@ constructor(
val iconViewModel: PromptIconViewModel =
PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+ private val _isIconViewLoaded = MutableStateFlow(false)
+
+ /**
+ * For prompts with an iconView, false until the prompt's iconView animation has been loaded in
+ * the view, otherwise true by default. Used for BiometricViewSizeBinder to wait for the icon
+ * asset to be loaded before determining the prompt size.
+ */
+ val isIconViewLoaded: Flow<Boolean> =
+ combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
+ ->
+ if (credentialKind is PromptKind.Biometric) {
+ isIconViewLoaded
+ } else {
+ true
+ }
+ }
+
+ // Sets whether the prompt's iconView animation has been loaded in the view yet.
+ fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
+ _isIconViewLoaded.value = iconViewLoaded
+ }
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
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 724c0fe1e4e4..1095abe24b47 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
@@ -19,7 +19,6 @@ package com.android.systemui.bouncer.domain.interactor
import android.content.Context
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.classifier.FalsingClassifier
@@ -29,17 +28,15 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/** Encapsulates business logic and application state accessing use-cases. */
@@ -52,33 +49,12 @@ constructor(
private val repository: BouncerRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
- flags: SceneContainerFlags,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
) {
-
- /** The user-facing message to show in the bouncer. */
- val message: StateFlow<String?> =
- combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
- messageOrLockoutMessage(message, lockout)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- messageOrLockoutMessage(
- repository.message.value,
- authenticationInteractor.lockout.value,
- )
- )
-
- /**
- * The current authentication lockout (aka "throttling") state, set when the user has to wait
- * before being able to try another authentication attempt. `null` indicates lockout isn't
- * active.
- */
- val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
+ /** The user-facing message to show in the bouncer when lockout is not active. */
+ val message: StateFlow<String?> = repository.message
/** Whether the auto confirm feature is enabled for the currently-selected user. */
val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -101,18 +77,13 @@ constructor(
/** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */
val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser
- init {
- if (flags.isEnabled()) {
- // Clear the message if moved from locked-out to no-longer locked-out.
- applicationScope.launch {
- lockout.pairwise().collect { (previous, current) ->
- if (previous != null && current == null) {
- clearMessage()
- }
- }
+ /** Emits a [Unit] each time a lockout is started for the selected user. */
+ val onLockoutStarted: Flow<Unit> =
+ authenticationInteractor.onAuthenticationResult
+ .filter { successfullyAuthenticated ->
+ !successfullyAuthenticated && authenticationInteractor.lockoutEndTimestamp != null
}
- }
- }
+ .map {}
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
@@ -188,7 +159,7 @@ constructor(
}
if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) {
- // We authenticate sim in SimInteractor
+ // SIM is authenticated in SimBouncerInteractor.
return AuthenticationResult.SKIPPED
}
@@ -196,18 +167,20 @@ constructor(
// view-models, whose lifecycle (and thus scope) is shorter than this interactor.
// This allows the task to continue running properly even when the calling scope has been
// cancelled.
- return applicationScope
- .async {
- val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm)
- if (
- authResult == AuthenticationResult.FAILED ||
- (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
- ) {
- showErrorMessage()
- }
- authResult
- }
- .await()
+ val authResult =
+ applicationScope
+ .async { authenticationInteractor.authenticate(input, tryAutoConfirm) }
+ .await()
+
+ if (authenticationInteractor.lockoutEndTimestamp != null) {
+ clearMessage()
+ } else if (
+ authResult == AuthenticationResult.FAILED ||
+ (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
+ ) {
+ showErrorMessage()
+ }
+ return authResult
}
/**
@@ -250,19 +223,4 @@ constructor(
else -> ""
}
}
-
- private fun messageOrLockoutMessage(
- message: String?,
- lockoutModel: AuthenticationLockoutModel?,
- ): String {
- return when {
- lockoutModel != null ->
- applicationContext.getString(
- com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
- lockoutModel.remainingSeconds,
- )
- message != null -> message
- else -> ""
- }
- }
}
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 4b1434323886..be6cf85a5a0e 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
@@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
+import com.android.internal.R
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
@@ -34,19 +35,24 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
+import com.android.systemui.util.time.SystemClock
import dagger.Module
import dagger.Provides
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.job
@@ -58,13 +64,14 @@ class BouncerViewModel(
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
- authenticationInteractor: AuthenticationInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
flags: SceneContainerFlags,
selectedUser: Flow<UserViewModel>,
users: Flow<List<UserViewModel>>,
userSwitcherMenu: Flow<List<UserActionViewModel>>,
actionButtonInteractor: BouncerActionButtonInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ private val clock: SystemClock,
) {
val selectedUserImage: StateFlow<Bitmap?> =
selectedUser
@@ -104,15 +111,6 @@ class BouncerViewModel(
val isUserSwitcherVisible: Boolean
get() = bouncerInteractor.isUserSwitcherVisible
- private val isInputEnabled: StateFlow<Boolean> =
- bouncerInteractor.lockout
- .map { it == null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = bouncerInteractor.lockout.value == null,
- )
-
// Handle to the scope of the child ViewModel (stored in [authMethod]).
private var childViewModelScope: CoroutineScope? = null
private val _dialogMessage = MutableStateFlow<String?>(null)
@@ -138,19 +136,22 @@ class BouncerViewModel(
*/
val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
+ /**
+ * A message shown when the user has attempted the wrong credential too many times and now must
+ * wait a while before attempting to authenticate again.
+ *
+ * This is updated every second (countdown) during the lockout duration. When lockout is not
+ * active, this is `null` and no lockout message should be shown.
+ */
+ private val lockoutMessage = MutableStateFlow<String?>(null)
+
/** The user-facing message to show in the bouncer. */
val message: StateFlow<MessageViewModel> =
- combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
- toMessageViewModel(message, isLockedOut = lockout != null)
- }
+ combine(bouncerInteractor.message, lockoutMessage) { _, _ -> createMessageViewModel() }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue =
- toMessageViewModel(
- message = bouncerInteractor.message.value,
- isLockedOut = bouncerInteractor.lockout.value != null,
- ),
+ initialValue = createMessageViewModel(),
)
/**
@@ -194,31 +195,78 @@ class BouncerViewModel(
initialValue = isFoldSplitRequired(authMethodViewModel.value),
)
+ private val isInputEnabled: StateFlow<Boolean> =
+ lockoutMessage
+ .map { it == null }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = authenticationInteractor.lockoutEndTimestamp == null,
+ )
+
+ private var lockoutCountdownJob: Job? = null
+
init {
if (flags.isEnabled()) {
applicationScope.launch {
- combine(bouncerInteractor.lockout, authMethodViewModel) {
- lockout,
- authMethodViewModel ->
- if (lockout != null && authMethodViewModel != null) {
+ bouncerInteractor.onLockoutStarted.collect {
+ showLockoutDialog()
+ startLockoutCountdown()
+ }
+ }
+
+ applicationScope.launch {
+ // Update the lockout countdown whenever the selected user is switched.
+ selectedUser.collect { startLockoutCountdown() }
+ }
+ }
+ }
+
+ /** Notifies that the dialog has been dismissed by the user. */
+ fun onDialogDismissed() {
+ _dialogMessage.value = null
+ }
+
+ private fun showLockoutDialog() {
+ applicationScope.launch {
+ val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
+ _dialogMessage.value =
+ authMethodViewModel.value?.lockoutMessageId?.let { messageId ->
+ applicationContext.getString(
+ messageId,
+ failedAttempts,
+ remainingLockoutSeconds()
+ )
+ }
+ }
+ }
+
+ /** Shows the countdown message and refreshes it every second. */
+ private fun startLockoutCountdown() {
+ lockoutCountdownJob?.cancel()
+ lockoutCountdownJob =
+ applicationScope.launch {
+ do {
+ val remainingSeconds = remainingLockoutSeconds()
+ lockoutMessage.value =
+ if (remainingSeconds > 0) {
applicationContext.getString(
- authMethodViewModel.lockoutMessageId,
- lockout.failedAttemptCount,
- lockout.remainingSeconds,
+ R.string.lockscreen_too_many_failed_attempts_countdown,
+ remainingSeconds,
)
} else {
null
}
- }
- .distinctUntilChanged()
- .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
+ delay(1.seconds)
+ } while (remainingSeconds > 0)
+ lockoutCountdownJob = null
}
- }
}
- /** Notifies that the dialog has been dismissed by the user. */
- fun onDialogDismissed() {
- _dialogMessage.value = null
+ private fun remainingLockoutSeconds(): Int {
+ val endTimestampMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ val remainingMs = max(0, endTimestampMs - clock.elapsedRealtime())
+ return ceil(remainingMs / 1000f).toInt()
}
private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
@@ -229,12 +277,11 @@ class BouncerViewModel(
return authMethod !is PasswordBouncerViewModel
}
- private fun toMessageViewModel(
- message: String?,
- isLockedOut: Boolean,
- ): MessageViewModel {
+ private fun createMessageViewModel(): MessageViewModel {
+ val isLockedOut = lockoutMessage.value != null
return MessageViewModel(
- text = message ?: "",
+ // A lockout message takes precedence over the non-lockout message.
+ text = lockoutMessage.value ?: bouncerInteractor.message.value ?: "",
isUpdateAnimated = !isLockedOut,
)
}
@@ -328,6 +375,7 @@ object BouncerViewModelModule {
userSwitcherViewModel: UserSwitcherViewModel,
actionButtonInteractor: BouncerActionButtonInteractor,
simBouncerInteractor: SimBouncerInteractor,
+ clock: SystemClock,
): BouncerViewModel {
return BouncerViewModel(
applicationContext = applicationContext,
@@ -341,6 +389,7 @@ object BouncerViewModelModule {
userSwitcherMenu = userSwitcherViewModel.menu,
actionButtonInteractor = actionButtonInteractor,
simBouncerInteractor = simBouncerInteractor,
+ clock = clock,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index b68271767fc2..8e14778f1aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -56,13 +56,11 @@ class PasswordBouncerViewModel(
/** Whether the UI should request focus on the text field element. */
val isTextFieldFocusRequested =
- combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
- throttling == null && !hasFocus
- }
+ combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
+ initialValue = isInputEnabled.value && !isTextFieldFocused.value,
)
override fun onHidden() {
@@ -104,7 +102,7 @@ class PasswordBouncerViewModel(
* hidden.
*/
suspend fun onImeVisibilityChanged(isVisible: Boolean) {
- if (isImeVisible && !isVisible && interactor.lockout.value == null) {
+ if (isImeVisible && !isVisible && isInputEnabled.value) {
interactor.onImeHiddenByUser()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index a12db6f8f346..779446d3cd37 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -117,10 +117,10 @@ interface CommunalWidgetDao {
fun updateItemRank(itemUid: Long, order: Int)
@Transaction
- fun updateWidgetOrder(ids: List<Int>) {
- ids.forEachIndexed { index, it ->
- val widget = getWidgetByIdNow(it)
- updateItemRank(widget.itemId, ids.size - index)
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ widgetIdToPriorityMap.forEach { (id, priority) ->
+ val widget = getWidgetByIdNow(id)
+ updateItemRank(widget.itemId, priority)
}
}
@@ -129,7 +129,7 @@ interface CommunalWidgetDao {
return insertWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- insertItemRank(priority),
+ itemId = insertItemRank(priority),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ded5581a3034..d1bbe5743a24 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -62,8 +62,12 @@ interface CommunalWidgetRepository {
/** Delete a widget by id from app widget service and the database. */
fun deleteWidget(widgetId: Int) {}
- /** Update the order of widgets in the database. */
- fun updateWidgetOrder(ids: List<Int>) {}
+ /**
+ * Update the order of widgets in the database.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -168,11 +172,11 @@ constructor(
}
}
- override fun updateWidgetOrder(ids: List<Int>) {
+ override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
applicationScope.launch(bgDispatcher) {
- communalWidgetDao.updateWidgetOrder(ids)
+ communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = ids.toString()
+ str1 = widgetIdToPriorityMap.toString()
}
}
}
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 e342c6bca6fa..0f4e583eda45 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
@@ -102,8 +102,13 @@ constructor(
/** Delete a widget by id. */
fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
- /** Reorder widgets. The order in the list will be their display order in the hub. */
- fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
+ /**
+ * Reorder the widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index bb9b4b5f522f..3ae522970365 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import java.util.UUID
/** Encapsulates data for a communal content. */
sealed interface CommunalContentModel {
@@ -39,6 +40,13 @@ sealed interface CommunalContentModel {
override val size = CommunalContentSize.HALF
}
+ /** A placeholder item representing a new widget being added */
+ class WidgetPlaceholder : CommunalContentModel {
+ override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
class Tutorial(
id: Int,
override val size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 708f137017ca..577e40412e4e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
@@ -53,6 +54,13 @@ abstract class BaseCommunalViewModel(
communalInteractor.setTransitionState(transitionState)
}
+ /**
+ * Called when a widget is added via drag and drop from the widget picker into the communal hub.
+ */
+ fun onAddWidget(componentName: ComponentName, priority: Int) {
+ communalInteractor.addWidget(componentName, priority)
+ }
+
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
// touches anymore.
/** Called when a touch is received outside the edge swipe area when hub mode is closed. */
@@ -82,8 +90,14 @@ abstract class BaseCommunalViewModel(
/** Called as the UI requests deleting a widget. */
open fun onDeleteWidget(id: Int) {}
- /** Called as the UI requests reordering widgets. */
- open fun onReorderWidgets(ids: List<Int>) {}
+ /**
+ * Called as the UI requests reordering widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
+ * add a new item in the middle, provide the priorities of existing widgets as if the new item
+ * existed, and then, call [onAddWidget] to add the new item at intended order.
+ */
+ open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c82e00038b34..368df9ea3693 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -47,5 +47,6 @@ constructor(
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
- override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+ override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 887b18cfe4c9..c936c636f5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,8 +16,9 @@
package com.android.systemui.communal.widgets
-import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
@@ -39,10 +40,9 @@ constructor(
private var windowManagerService: IWindowManager? = null,
) : ComponentActivity() {
companion object {
- /**
- * Intent extra name for the {@link AppWidgetProviderInfo} of a widget to add to hub mode.
- */
- const val ADD_WIDGET_INFO = "add_widget_info"
+ private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
+ private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
+ private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
private const val TAG = "EditWidgetsActivity"
}
@@ -50,15 +50,23 @@ constructor(
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
- result.data
- ?.let {
- it.getParcelableExtra(
- ADD_WIDGET_INFO,
- AppWidgetProviderInfo::class.java
- )
+ result.data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ intent
+ .getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ ?.let { communalInteractor.addWidget(it, 0) }
+ ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
}
- ?.let { communalInteractor.addWidget(it.provider, 0) }
- ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -75,9 +83,29 @@ constructor(
activity = this,
viewModel = communalViewModel,
onOpenWidgetPicker = {
- addWidgetActivityLauncher.launch(
- Intent(applicationContext, WidgetPickerActivity::class.java)
- )
+ val localPackageManager: PackageManager = getPackageManager()
+ val intent =
+ Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
+ localPackageManager
+ .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ ?.activityInfo
+ ?.packageName
+ ?.let { packageName ->
+ try {
+ addWidgetActivityLauncher.launch(
+ Intent(Intent.ACTION_PICK).also {
+ it.setPackage(packageName)
+ it.putExtra(
+ EXTRA_FILTER_STRATEGY,
+ FILTER_STRATEGY_GLANCEABLE_HUB
+ )
+ }
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to launch widget picker activity", e)
+ }
+ }
+ ?: run { Log.e(TAG, "Couldn't resolve launcher package name") }
},
onEditDone = {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
deleted file mode 100644
index a26afc86aa2e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetPickerActivity.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.widgets
-
-import android.appwidget.AppWidgetManager
-import android.appwidget.AppWidgetProviderInfo
-import android.content.Intent
-import android.graphics.Color
-import android.os.Bundle
-import android.util.Log
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.activity.ComponentActivity
-import androidx.core.view.setMargins
-import androidx.core.view.setPadding
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-/**
- * An Activity responsible for displaying a list of widgets to add to the hub mode grid. This is
- * essentially a placeholder until Launcher's widget picker can be used.
- */
-class WidgetPickerActivity
-@Inject
-constructor(
- private val appWidgetManager: AppWidgetManager,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContentView(R.layout.widget_picker)
- loadWidgets()
- }
-
- private fun loadWidgets() {
- val containerView: ViewGroup? = findViewById(R.id.widgets_container)
- containerView?.apply {
- try {
- appWidgetManager
- .getInstalledProviders(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- ?.stream()
- ?.forEach { widgetInfo ->
- val activity = this@WidgetPickerActivity
- (widgetInfo.loadPreviewImage(activity, 0)
- ?: widgetInfo.loadIcon(activity, 0))
- ?.let {
- addView(
- ImageView(activity).also { v ->
- v.setImageDrawable(it)
- v.setBackgroundColor(WIDGET_PREVIEW_BACKGROUND_COLOR)
- v.setPadding(WIDGET_PREVIEW_PADDING)
- v.layoutParams =
- LinearLayout.LayoutParams(
- WIDGET_PREVIEW_SIZE,
- WIDGET_PREVIEW_SIZE
- )
- .also { lp ->
- lp.setMargins(WIDGET_PREVIEW_MARGINS)
- }
- v.setOnClickListener {
- setResult(
- RESULT_OK,
- Intent()
- .putExtra(
- EditWidgetsActivity.ADD_WIDGET_INFO,
- widgetInfo
- )
- )
- finish()
- }
- }
- )
- }
- }
- } catch (e: RuntimeException) {
- Log.e(TAG, "Exception fetching widget providers", e)
- }
- }
- }
-
- companion object {
- private const val WIDGET_PREVIEW_SIZE = 600
- private const val WIDGET_PREVIEW_MARGINS = 32
- private const val WIDGET_PREVIEW_PADDING = 32
- private val WIDGET_PREVIEW_BACKGROUND_COLOR = Color.rgb(216, 225, 220)
- private const val TAG = "WidgetPickerActivity"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4b27af1fc989..9afd5ede0b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@ import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.communal.widgets.WidgetPickerActivity;
import com.android.systemui.contrast.ContrastDialogActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
@@ -158,12 +157,6 @@ public abstract class DefaultActivityBinder {
@ClassKey(EditWidgetsActivity.class)
public abstract Activity bindEditWidgetsActivity(EditWidgetsActivity activity);
- /** Inject into WidgetPickerActivity. */
- @Binds
- @IntoMap
- @ClassKey(WidgetPickerActivity.class)
- public abstract Activity bindWidgetPickerActivity(WidgetPickerActivity activity);
-
/** Inject into SwitchToManagedProfileForCallActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 236c5b8ed2d7..50f861fad9e4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,6 +23,8 @@ import android.content.Context;
import android.hardware.SensorPrivacyManager;
import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.SystemActionsModule;
import com.android.systemui.battery.BatterySaverModule;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -34,6 +36,7 @@ import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.reardisplay.RearDisplayModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
@@ -59,6 +62,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.toast.ToastModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -89,19 +93,23 @@ import javax.inject.Named;
CollapsedStatusBarFragmentStartableModule.class,
GestureModule.class,
HeadsUpModule.class,
+ KeyboardShortcutsModule.class,
MediaModule.class,
MultiUserUtilsModule.class,
NavigationBarControllerModule.class,
PowerModule.class,
QSModule.class,
- ShadeModule.class,
+ RearDisplayModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
- SceneContainerFrameworkModule.class,
+ ScreenDecorationsModule.class,
+ SystemActionsModule.class,
+ ShadeModule.class,
StartCentralSurfacesModule.class,
+ SceneContainerFrameworkModule.class,
+ ToastModule.class,
VolumeModule.class,
- WallpaperModule.class,
- KeyboardShortcutsModule.class
+ WallpaperModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d041acb4601a..ac71664e5590 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -19,12 +19,9 @@ package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
-import com.android.systemui.ScreenDecorations
import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
@@ -46,10 +43,6 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.power.PowerUI
-import com.android.systemui.reardisplay.RearDisplayDialogController
-import com.android.systemui.recents.Recents
-import com.android.systemui.recents.ScreenPinningRequest
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -61,11 +54,9 @@ import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
-import com.android.systemui.volume.VolumeUI
import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
@@ -74,7 +65,12 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
/**
- * Collection of {@link CoreStartable}s that should be run on AOSP.
+ * DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
+ *
+ * Add a feature specific daggger module for what you are working on. Bind your CoreStartable there.
+ * Include that module where it is needed.
+ *
+ * @deprecated
*/
@Module(
includes = [
@@ -85,12 +81,6 @@ import dagger.multibindings.IntoMap
]
)
abstract class SystemUICoreStartableModule {
- /** Inject into AuthController. */
- @Binds
- @IntoMap
- @ClassKey(AuthController::class)
- abstract fun bindAuthController(service: AuthController): CoreStartable
-
/** Inject into BiometricNotificationService */
@Binds
@IntoMap
@@ -158,18 +148,6 @@ abstract class SystemUICoreStartableModule {
@PerUser
abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
- /** Inject into PowerUI. */
- @Binds
- @IntoMap
- @ClassKey(PowerUI::class)
- abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
- /** Inject into Recents. */
- @Binds
- @IntoMap
- @ClassKey(Recents::class)
- abstract fun bindRecents(sysui: Recents): CoreStartable
-
/** Inject into ImmersiveModeConfirmation. */
@Binds
@IntoMap
@@ -182,12 +160,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(RingtonePlayer::class)
abstract fun bind(sysui: RingtonePlayer): CoreStartable
- /** Inject into ScreenDecorations. */
- @Binds
- @IntoMap
- @ClassKey(ScreenDecorations::class)
- abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
-
/** Inject into GesturePointerEventHandler. */
@Binds
@IntoMap
@@ -218,23 +190,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(StorageNotification::class)
abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
- /** Inject into SystemActions. */
- @Binds
- @IntoMap
- @ClassKey(SystemActions::class)
- abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
-
/** Inject into ThemeOverlayController. */
@Binds
@IntoMap
@ClassKey(ThemeOverlayController::class)
abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
- /** Inject into ToastUI. */
- @Binds
- @IntoMap
- @ClassKey(ToastUI::class)
- abstract fun bindToastUI(service: ToastUI): CoreStartable
/** Inject into MediaOutputSwitcherDialogUI. */
@Binds
@@ -242,12 +203,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(MediaOutputSwitcherDialogUI::class)
abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
- /** Inject into VolumeUI. */
- @Binds
- @IntoMap
- @ClassKey(VolumeUI::class)
- abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
/** Inject into Magnification. */
@Binds
@IntoMap
@@ -293,11 +248,6 @@ abstract class SystemUICoreStartableModule {
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
- /** Inject into RearDisplayDialogController) */
- @Binds
- @IntoMap
- @ClassKey(RearDisplayDialogController::class)
- abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
/** Inject into StylusUsiPowerStartable) */
@Binds
@@ -361,9 +311,4 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(ScreenPinningRequest::class)
- abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 47be8ab0c0a2..f6a9570fc94c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -159,7 +159,7 @@ constructor(
fun attemptDeviceEntry() {
// TODO (b/307768356),
// 1. Check if the device is already authenticated by trust agent/passive biometrics
- // 2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
+ // 2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
// 3. For face auth only setups trigger face auth, delay transitioning to bouncer for
// a small amount of time.
// 4. Transition to bouncer scene
@@ -197,8 +197,8 @@ constructor(
init {
if (flags.isEnabled()) {
applicationScope.launch {
- authenticationInteractor.authenticationChallengeResult.collectLatest { successful ->
- if (successful) {
+ authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful ->
+ if (isSuccessful) {
repository.reportSuccessfulAuthentication()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
index 0113628c30ca..ef2537c1bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
@@ -15,40 +15,32 @@
*
*/
-package com.android.systemui.keyguard.ui.binder
+package com.android.systemui.deviceentry.ui.binder
-import android.content.res.ColorStateList
-import android.widget.ImageView
+import android.annotation.SuppressLint
+import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
-object UdfpsBackgroundViewBinder {
+object UdfpsAccessibilityOverlayBinder {
- /**
- * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
- * [UdfpsFingerprintViewBinder].
- */
+ /** Forwards hover events to the view model to make guided announcements for accessibility. */
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
- view: ImageView,
- viewModel: BackgroundViewModel,
+ view: UdfpsAccessibilityOverlay,
+ viewModel: UdfpsAccessibilityOverlayViewModel,
) {
- view.alpha = 0f
+ view.setOnHoverListener { v, event -> viewModel.onHoverEvent(v, event) }
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- view.imageTintList = ColorStateList.valueOf(it.color)
- }
- }
+ // Repeat on CREATED because we update the visibility of the view
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ viewModel.visible.collect { visible -> view.isInvisible = !visible }
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
index 7e35360e30ff..7be323073692 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/shared/model/NotificationSettingsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
@@ -12,13 +12,12 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.shared.notifications.shared.model
+package com.android.systemui.deviceentry.ui.view
+
+import android.content.Context
+import android.view.View
-/** Models notification settings. */
-data class NotificationSettingsModel(
- /** Whether notifications are shown on the lock screen. */
- val isShowNotificationsOnLockScreenEnabled: Boolean = false,
-)
+/** Overlay to handle under-fingerprint sensor accessibility events. */
+class UdfpsAccessibilityOverlay(context: Context?) : View(context)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
new file mode 100644
index 000000000000..80684b442c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.ui.viewmodel
+
+import android.graphics.Point
+import android.view.MotionEvent
+import android.view.View
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Models the UI state for the UDFPS accessibility overlay */
+@ExperimentalCoroutinesApi
+class UdfpsAccessibilityOverlayViewModel
+@Inject
+constructor(
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ accessibilityInteractor: AccessibilityInteractor,
+ deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
+) {
+ private val udfpsUtils = UdfpsUtils()
+ private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
+ udfpsOverlayInteractor.udfpsOverlayParams
+
+ /** Overlay is only visible if touch exploration is enabled and UDFPS can be used. */
+ val visible: Flow<Boolean> =
+ accessibilityInteractor.isTouchExplorationEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ combine(
+ deviceEntryFgIconViewModel.viewModel,
+ deviceEntryIconViewModel.deviceEntryViewAlpha,
+ ) { iconViewModel, alpha ->
+ iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT &&
+ !iconViewModel.useAodVariant &&
+ alpha == 1f
+ }
+ } else {
+ flowOf(false)
+ }
+ }
+
+ /** Give directional feedback to help the user authenticate with UDFPS. */
+ fun onHoverEvent(v: View, event: MotionEvent): Boolean {
+ val overlayParams = udfpsOverlayParams.value
+ val scaledTouch: Point =
+ udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
+
+ if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
+ // view only receives motionEvents when [visible] which requires touchExplorationEnabled
+ val announceStr =
+ udfpsUtils.onTouchOutsideOfSensorArea(
+ /* touchExplorationEnabled */ true,
+ v.context,
+ scaledTouch.x,
+ scaledTouch.y,
+ overlayParams,
+ )
+ if (announceStr != null) {
+ v.announceForAccessibility(announceStr)
+ }
+ }
+ // always let the motion events go through to underlying views
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index 6560ee3408d4..14fda5e96a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -73,9 +73,19 @@ abstract class FlagDependenciesBase(
) {
val isMet = !alphaEnabled || betaEnabled
override fun toString(): String {
- val isMetBullet = if (isMet) "+" else "-"
- return "$isMetBullet $alphaName ($alphaEnabled) DEPENDS ON $betaName ($betaEnabled)"
+ val prefix =
+ when {
+ !isMet -> " [NOT MET]"
+ alphaEnabled -> " [met]"
+ betaEnabled -> " [ready]"
+ else -> "[not ready]"
+ }
+ val alphaState = if (alphaEnabled) "enabled" else "disabled"
+ val betaState = if (betaEnabled) "enabled" else "disabled"
+ return "$prefix $alphaName ($alphaState) DEPENDS ON $betaName ($betaState)"
}
+ /** Used whe posting a notification of unmet dependencies */
+ fun shortUnmetString(): String = "$alphaName DEPENDS ON $betaName"
}
protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) =
@@ -124,7 +134,7 @@ constructor(
unmet: List<FlagDependenciesBase.Dependency>
) {
val title = "Invalid flag dependencies: ${unmet.size} of ${all.size}"
- val details = unmet.joinToString("\n")
+ val details = unmet.joinToString("\n") { it.shortUnmetString() }
Log.e("FlagDependencies", "$title:\n$details")
val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
val notification =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 5b9509d3cd8c..1b350055e8de 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -17,11 +17,11 @@ package com.android.systemui.flags
import android.provider.DeviceConfig
import com.android.internal.annotations.Keep
-import com.android.systemui.res.R
import com.android.systemui.flags.FlagsFactory.releasedFlag
import com.android.systemui.flags.FlagsFactory.resourceBooleanFlag
import com.android.systemui.flags.FlagsFactory.sysPropBooleanFlag
import com.android.systemui.flags.FlagsFactory.unreleasedFlag
+import com.android.systemui.res.R
/**
* List of [Flag] objects for use in SystemUI.
@@ -585,10 +585,6 @@ object Flags {
@JvmField
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag("split_shade_subpixel_optimization")
- // TODO(b/288868056): Tracking Bug
- @JvmField
- val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher")
-
// TODO(b/278761837): Tracking Bug
@JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter")
@@ -604,19 +600,11 @@ object Flags {
@JvmField
val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
- // TODO(b/283084712): Tracking Bug
- @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
-
// TODO(b/283447257): Tracking bug
@JvmField
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
unreleasedFlag("bigpicture_notification_lazy_loading")
- // TODO(b/292062937): Tracking bug
- @JvmField
- val NOTIFICATION_CLEARABLE_REFACTOR =
- unreleasedFlag("notification_clearable_refactor")
-
// TODO(b/283740863): Tracking Bug
@JvmField
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361064a7..cfa5294567b7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@ class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener
SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
}
}
+
+ /** The arrow navigation that was operating the slider has stopped. */
+ fun onArrowUp() {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63cd483..10098faaa05e 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@ class SeekableSliderTracker(
override suspend fun iterateState(event: SliderEvent) {
when (currentState) {
- SliderState.IDLE -> handleIdle(event.type)
+ SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
SliderState.WAIT -> handleWait(event.type, event.currentProgress)
SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@ class SeekableSliderTracker(
SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+ handleArrowContinuous(event.type, event.currentProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
}
latestProgress = event.currentProgress
}
- private fun handleIdle(newEventType: SliderEventType) {
+ private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
timerJob = launchTimer()
// The WAIT state will wait for the timer to complete or a slider progress to occur.
// This will disambiguate between an imprecise touch that acquires the slider handle,
// and a select and jump operation in the slider track.
setState(SliderState.WAIT)
+ } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+ val state =
+ if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVED_ONCE
+ setState(state)
}
}
@@ -176,6 +185,13 @@ class SeekableSliderTracker(
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+ executeOnBookend()
+ // This transitory execution must also reset the state
+ resetState()
+ }
else -> {}
}
}
@@ -204,6 +220,43 @@ class SeekableSliderTracker(
currentProgress <= config.lowerBookendThreshold
}
+ private fun handleArrowOnce(newEventType: SliderEventType) {
+ val nextState =
+ when (newEventType) {
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+ val reachedBookend = bookendReached(currentProgress)
+ val nextState =
+ when (newEventType) {
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+ if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowBookend() = setState(SliderState.IDLE)
+
@VisibleForTesting
fun setState(state: SliderState) {
currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e27763ba8..4a63941b3f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@ enum class SliderEventType {
/* The slider has stopped tracking touch events. */
STOPPED_TRACKING_TOUCH,
/* The external (not touch) stimulus that was modifying the slider progress has stopped. */
- EXTERNAL_STIMULUS_RELEASE,
+ ARROW_UP,
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e67036b..de6ddd7168e5 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@ enum class SliderState {
DRAG_HANDLE_REACHED_BOOKEND,
/* A location in the slider track has been selected. */
JUMP_TRACK_LOCATION_SELECTED,
- /* The slider handled moved to a bookend after it was selected. */
+ /* The slider handle moved to a bookend after it was selected. */
JUMP_BOOKEND_SELECTED,
+ /** The slider handle moved due to single select-and-arrow operation */
+ ARROW_HANDLE_MOVED_ONCE,
+ /** The slider handle moves continuously due to constant select-and-arrow operations */
+ ARROW_HANDLE_MOVES_CONTINUOUSLY,
+ /** The slider handle reached a bookend due to a select-and-arrow operation */
+ ARROW_HANDLE_REACHED_BOOKEND,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index c490ce7db843..342325ffee91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -30,7 +30,7 @@ import android.net.Uri
import android.os.Binder
import android.os.Bundle
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.runBlocking
+import com.android.app.tracing.coroutines.runBlocking
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 4d60dd0bea62..17d78365417b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -626,17 +626,19 @@ constructor(
faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
return
}
- detectCancellationSignal?.cancel()
- detectCancellationSignal = CancellationSignal()
withContext(mainDispatcher) {
// We always want to invoke face detect in the main thread.
faceAuthLogger.faceDetectionStarted()
- faceManager?.detectFace(
- checkNotNull(detectCancellationSignal),
- detectionCallback,
- SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
- .toFaceAuthenticateOptions()
- )
+ detectCancellationSignal?.cancel()
+ detectCancellationSignal = CancellationSignal()
+ detectCancellationSignal?.let {
+ faceManager?.detectFace(
+ it,
+ detectionCallback,
+ SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
+ .toFaceAuthenticateOptions()
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f63717938..ecf78d550a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/**
* Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@ import kotlinx.coroutines.flow.merge
class DeviceEntrySideFpsOverlayInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@ constructor(
init {
if (!DeviceEntryUdfpsRefactor.isEnabled) {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ applicationScope.launch {
+ deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+ if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 9fe5c3f53d96..cecc6537e16e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7882a9758105..388834597f60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,7 +22,7 @@ import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.withContext
+import com.android.app.tracing.coroutines.withContext
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 64ff3b0c238a..12775854c737 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,11 +32,8 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -68,8 +65,6 @@ constructor(
* in the range of [0, 1]. View animations should begin and end within a subset of this
* range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
* valid.
- *
- * Will produce a [SharedFlow], so that identical animations can use the same value.
*/
fun sharedFlow(
duration: Duration,
@@ -80,7 +75,7 @@ constructor(
onFinish: (() -> Float)? = null,
interpolator: Interpolator = LINEAR,
name: String? = null
- ): SharedFlow<Float> {
+ ): Flow<Float> {
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
@@ -137,7 +132,6 @@ constructor(
value
}
.filterNotNull()
- .shareIn(scope, SharingStarted.WhileSubscribed())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index d12d193aa43b..c749818a05e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
object AlternateBouncerUdfpsViewBinder {
@@ -71,10 +72,12 @@ object AlternateBouncerUdfpsViewBinder {
bgView.visibility = View.VISIBLE
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.bgViewModel.collect { bgViewModel ->
- bgView.alpha = bgViewModel.alpha
- bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ launch {
+ viewModel.bgColor.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
+ }
}
+ launch { viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha } }
}
}
}
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 dcf4284438bf..a02e8ac84a75 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
@@ -131,10 +131,10 @@ object DeviceEntryIconViewBinder {
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
launch {
- bgViewModel.viewModel.collect { bgViewModel ->
- bgView.alpha = bgViewModel.alpha
- bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ bgViewModel.color.collect { color ->
+ bgView.imageTintList = ColorStateList.valueOf(color)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206498e4..96e83b0ca0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@ object KeyguardBottomAreaViewBinder {
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
settingsMenu.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = settingsMenu,
viewModel = viewModel.settingsMenuViewModel,
)
)
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 7d290c3c61fd..05fe0b25381d 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
@@ -32,8 +32,6 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import kotlinx.coroutines.launch
-private val TAG = KeyguardClockViewBinder::class.simpleName
-
object KeyguardClockViewBinder {
@JvmStatic
fun bind(
@@ -74,12 +72,6 @@ object KeyguardClockViewBinder {
applyConstraints(clockSection, keyguardRootView, true)
}
}
- launch {
- if (!migrateClocksToBlueprint()) return@launch
- viewModel.hasCustomWeatherDataDisplay.collect {
- applyConstraints(clockSection, keyguardRootView, true)
- }
- }
}
}
}
@@ -132,7 +124,7 @@ object KeyguardClockViewBinder {
fun applyConstraints(
clockSection: ClockSection,
rootView: ConstraintLayout,
- animated: Boolean
+ animated: Boolean,
) {
val constraintSet = ConstraintSet().apply { clone(rootView) }
clockSection.applyConstraints(constraintSet)
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 362e7e6d4770..fad0370a85d7 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
@@ -255,6 +255,7 @@ object KeyguardRootViewBinder {
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.CONFIRM,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
@@ -264,6 +265,7 @@ object KeyguardRootViewBinder {
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.REJECT,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c97013..c6dfcb00a809 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@ import android.graphics.PointF
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
class KeyguardSettingsButtonOnTouchListener(
- private val view: LaunchableLinearLayout,
private val viewModel: KeyguardSettingsMenuViewModel,
) : View.OnTouchListener {
@@ -41,8 +39,10 @@ class KeyguardSettingsButtonOnTouchListener(
MotionEvent.ACTION_UP -> {
view.isPressed = false
val distanceMoved =
- motionEvent
- .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+ motionEvent.rawDistanceFrom(
+ downPositionDisplayCoords.x,
+ downPositionDisplayCoords.y
+ )
val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
viewModel.onTouchGestureEnded(isClick)
if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e76c289..f67cb684b7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@ import kotlinx.coroutines.launch
object KeyguardSettingsViewBinder {
fun bind(
- parentView: View,
+ view: View,
viewModel: KeyguardSettingsMenuViewModel,
longPressViewModel: KeyguardLongPressViewModel,
- rootViewModel: KeyguardRootViewModel,
+ rootViewModel: KeyguardRootViewModel?,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@ object KeyguardSettingsViewBinder {
vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
view.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = view,
viewModel = viewModel,
)
)
@@ -96,7 +92,7 @@ object KeyguardSettingsViewBinder {
}
launch {
- rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
if (view.isVisible) {
val hitRect = Rect()
view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 954d2cf6ed8a..e36819b54524 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,13 +16,19 @@
package com.android.systemui.keyguard.ui.binder
+import android.transition.TransitionManager
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.launch
object KeyguardSmartspaceViewBinder {
@JvmStatic
@@ -30,15 +36,63 @@ object KeyguardSmartspaceViewBinder {
smartspaceSection: SmartspaceSection,
keyguardRootView: ConstraintLayout,
clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- clockViewModel.hasCustomWeatherDataDisplay.collect {
- val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
- smartspaceSection.applyConstraints(constraintSet)
- constraintSet.applyTo(keyguardRootView)
+ launch {
+ clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
+ ->
+ if (hasCustomWeatherDataDisplay) {
+ removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+ } else {
+ addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+ }
+ clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
+ smartspaceSection.applyConstraints(constraintSet)
+ TransitionManager.beginDelayedTransition(keyguardRootView)
+ constraintSet.applyTo(keyguardRootView)
+ }
}
}
}
}
+
+ private fun addDateWeatherToBurnInLayer(
+ constraintLayout: ConstraintLayout,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ if (
+ smartspaceViewModel.isSmartspaceEnabled &&
+ smartspaceViewModel.isDateWeatherDecoupled
+ ) {
+ val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
+ val weatherView =
+ constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
+ addView(weatherView)
+ addView(dateView)
+ }
+ }
+ }
+
+ private fun removeDateWeatherToBurnInLayer(
+ constraintLayout: ConstraintLayout,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ if (
+ smartspaceViewModel.isSmartspaceEnabled &&
+ smartspaceViewModel.isDateWeatherDecoupled
+ ) {
+ val dateView = smartspaceViewModel.dateView
+ val weatherView = smartspaceViewModel.weatherView
+ removeView(weatherView)
+ removeView(dateView)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
deleted file mode 100644
index 52d87d369083..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsAodFingerprintViewBinder {
-
- /**
- * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
- * [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: UdfpsAodViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.progress = burnInOffsets.progress
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
deleted file mode 100644
index d4621e6e2356..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsFingerprintViewBinder {
- private var udfpsIconColor = 0
-
- /**
- * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
- * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
- */
- @JvmStatic
- fun bind(
- view: LottieAnimationView,
- viewModel: FingerprintViewModel,
- ) {
- view.alpha = 0f
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.transition.collect {
- view.alpha = it.alpha
- view.scaleX = it.scale
- view.scaleY = it.scale
- if (udfpsIconColor != (it.color)) {
- udfpsIconColor = it.color
- view.invalidate()
- }
- }
- }
-
- launch {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- }
- }
-
- launch {
- viewModel.dozeAmount.collect { dozeAmount ->
- // Lottie progress represents: aod=0 to lockscreen=1
- view.progress = 1f - dozeAmount
- }
- }
-
- launch {
- viewModel.padding.collect { padding ->
- view.setPadding(padding, padding, padding, padding)
- }
- }
- }
- }
-
- // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
- view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
deleted file mode 100644
index aabb3f41e881..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.binder
-
-import android.view.View
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
-import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardInternalViewBinder {
-
- @JvmStatic
- fun bind(
- view: View,
- viewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- view.accessibilityDelegate = viewModel.accessibilityDelegate
-
- // bind child views
- UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
- UdfpsFingerprintViewBinder.bind(
- view.requireViewById(R.id.udfps_lockscreen_fp),
- fingerprintViewModel
- )
- UdfpsBackgroundViewBinder.bind(
- view.requireViewById(R.id.udfps_keyguard_fp_bg),
- backgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
deleted file mode 100644
index 475d26f1db54..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.graphics.RectF
-import android.view.View
-import android.widget.FrameLayout
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
-import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@ExperimentalCoroutinesApi
-object UdfpsKeyguardViewBinder {
- /**
- * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
- * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
- * [UdfpsAodFingerprintViewBinder].
- */
- @JvmStatic
- fun bind(
- view: UdfpsKeyguardView,
- viewModel: UdfpsKeyguardViewModel,
- udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
- aodViewModel: UdfpsAodViewModel,
- fingerprintViewModel: FingerprintViewModel,
- backgroundViewModel: BackgroundViewModel,
- ) {
- val layoutInflaterFinishListener =
- AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
- UdfpsKeyguardInternalViewBinder.bind(
- inflatedInternalView,
- udfpsKeyguardInternalViewModel,
- aodViewModel,
- fingerprintViewModel,
- backgroundViewModel,
- )
- val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
- lp.width = viewModel.sensorBounds.width()
- lp.height = viewModel.sensorBounds.height()
- val relativeToView =
- getBoundsRelativeToView(
- inflatedInternalView,
- RectF(viewModel.sensorBounds),
- )
- lp.setMarginsRelative(
- relativeToView.left.toInt(),
- relativeToView.top.toInt(),
- relativeToView.right.toInt(),
- relativeToView.bottom.toInt(),
- )
- parent!!.addView(inflatedInternalView, lp)
- }
- val inflater = AsyncLayoutInflater(view.context)
- inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
-
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
- isAodVisible,
- isFingerprintVisible ->
- isAodVisible || isFingerprintVisible
- }
- .collect { view.setVisible(it) }
- }
- }
- }
- }
-
- /**
- * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
- *
- * @param bounds RectF based off screen coordinates in current orientation
- */
- private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
- val pos: IntArray = view.locationOnScreen
- return RectF(
- bounds.left - pos[0],
- bounds.top - pos[1],
- bounds.right - pos[0],
- bounds.bottom - pos[1]
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 1c6a2abdcbe7..bc9671e65f24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -31,18 +31,21 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
+@ExperimentalCoroutinesApi
@SysUISingleton
@JvmSuppressWildcards
class DefaultKeyguardBlueprint
@@ -62,6 +65,7 @@ constructor(
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
clockSection: ClockSection,
smartspaceSection: SmartspaceSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
@@ -79,7 +83,8 @@ constructor(
aodBurnInSection,
communalTutorialIndicatorSection,
clockSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index bf7068220576..9b404338b9e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -29,14 +29,17 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotification
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Vertically aligns the shortcuts with the udfps. */
+@ExperimentalCoroutinesApi
@SysUISingleton
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
@@ -53,6 +56,7 @@ constructor(
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
+ udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -68,7 +72,8 @@ constructor(
splitShadeGuidelines,
aodNotificationIconsSection,
aodBurnInSection,
- defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
+ defaultDeviceEntrySection,
+ udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index f890ae612ccc..16539db648bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -30,6 +30,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
import com.android.systemui.util.kotlin.getOrNull
@@ -59,6 +61,8 @@ constructor(
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+ smartspaceSection: SmartspaceSection,
+ clockSection: SplitShadeClockSection,
) : KeyguardBlueprint {
override val id: String = ID
@@ -73,8 +77,10 @@ constructor(
splitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines,
aodNotificationIconsSection,
+ smartspaceSection,
aodBurnInSection,
communalTutorialIndicatorSection,
+ clockSection,
defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8166b454fcff..d89e1e41d1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -23,7 +23,6 @@ import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -38,7 +37,6 @@ constructor(
private val context: Context,
private val clockViewModel: KeyguardClockViewModel,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
- private val featureFlags: FeatureFlagsClassic,
) : KeyguardSection() {
lateinit var burnInLayer: Layer
@@ -59,6 +57,8 @@ constructor(
}
}
if (migrateClocksToBlueprint()) {
+ // weather and date parts won't be added here, cause their visibility doesn't align
+ // with others in burnInLayer
addSmartspaceViews(constraintLayout)
}
constraintLayout.addView(burnInLayer)
@@ -89,14 +89,6 @@ constructor(
val smartspaceView =
constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId)
addView(smartspaceView)
- if (smartspaceViewModel.isDateWeatherDecoupled) {
- val dateView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
- val weatherView =
- constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
- addView(weatherView)
- addView(dateView)
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 39a0547ded26..b429ab4fac0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -49,7 +48,6 @@ class AodNotificationIconsSection
constructor(
private val context: Context,
private val configurationState: ConfigurationState,
- private val featureFlags: FeatureFlagsClassic,
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -119,14 +117,8 @@ constructor(
}
constraintSet.apply {
if (migrateClocksToBlueprint()) {
- connect(
- nicId,
- TOP,
- smartspaceViewModel.smartspaceViewId,
- topAlignment,
- bottomMargin
- )
- setGoneMargin(nicId, topAlignment, bottomMargin)
+ connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin)
+ setGoneMargin(nicId, BOTTOM, bottomMargin)
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
}
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 1df920aab833..656c75c7bfec 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
@@ -27,7 +27,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -50,19 +50,21 @@ internal fun ConstraintSet.setAlpha(
alpha: Float,
) = views.forEach { view -> this.setAlpha(view.id, alpha) }
-class ClockSection
+open class ClockSection
@Inject
constructor(
private val clockInteractor: KeyguardClockInteractor,
- private val keyguardClockViewModel: KeyguardClockViewModel,
- val smartspaceViewModel: KeyguardSmartspaceViewModel,
+ protected val keyguardClockViewModel: KeyguardClockViewModel,
+ private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
- private val featureFlags: FeatureFlagsClassic,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
+ if (!Flags.migrateClocksToBlueprint()) {
+ return
+ }
KeyguardClockViewBinder.bind(
this,
constraintLayout,
@@ -72,6 +74,9 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
+ if (!Flags.migrateClocksToBlueprint()) {
+ return
+ }
clockInteractor.clock?.let { clock ->
constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
}
@@ -94,16 +99,6 @@ constructor(
}
}
- var largeClockEndGuideline = PARENT_ID
-
- // Return if largeClockEndGuideline changes,
- // and use it to decide whether to refresh blueprint
- fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean {
- val previousValue = largeClockEndGuideline
- largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline
- return previousValue != largeClockEndGuideline
- }
-
private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock)
else getSmallClockFace(clock)
@@ -113,10 +108,10 @@ constructor(
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
- fun applyDefaultConstraints(constraints: ConstraintSet) {
+ open fun applyDefaultConstraints(constraints: ConstraintSet) {
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END)
+ connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
new file mode 100644
index 000000000000..e1a33dea2257
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.Flags
+import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
+import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Positions the UDFPS accessibility overlay on the bottom half of the keyguard. */
+@ExperimentalCoroutinesApi
+class DefaultUdfpsAccessibilityOverlaySection
+@Inject
+constructor(
+ private val context: Context,
+ private val viewModel: UdfpsAccessibilityOverlayViewModel,
+) : KeyguardSection() {
+ private val viewId = R.id.udfps_accessibility_overlay
+ private var cachedConstraintLayout: ConstraintLayout? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ cachedConstraintLayout = constraintLayout
+ constraintLayout.addView(UdfpsAccessibilityOverlay(context).apply { id = viewId })
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {
+ UdfpsAccessibilityOverlayBinder.bind(
+ constraintLayout.findViewById(viewId)!!,
+ viewModel,
+ )
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ constraintSet.apply {
+ connect(viewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
+ connect(viewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
+
+ create(R.id.udfps_accessibility_overlay_top_guideline, ConstraintSet.HORIZONTAL)
+ setGuidelinePercent(R.id.udfps_accessibility_overlay_top_guideline, .5f)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ R.id.udfps_accessibility_overlay_top_guideline,
+ ConstraintSet.BOTTOM,
+ )
+
+ if (Flags.keyguardBottomAreaRefactor()) {
+ connect(
+ viewId,
+ ConstraintSet.BOTTOM,
+ R.id.keyguard_indication_area,
+ ConstraintSet.TOP,
+ )
+ } else {
+ connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
+ }
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ constraintLayout.removeView(viewId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 368b388062a1..8cd51cd3b1e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
-import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -36,7 +35,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
-class SmartspaceSection
+open class SmartspaceSection
@Inject
constructor(
val keyguardClockViewModel: KeyguardClockViewModel,
@@ -45,9 +44,13 @@ constructor(
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
) : KeyguardSection() {
- private var smartspaceView: View? = null
- private var weatherView: View? = null
- private var dateView: View? = null
+ var smartspaceView by keyguardSmartspaceViewModel::smartspaceView
+ var weatherView by keyguardSmartspaceViewModel::weatherView
+ var dateView by keyguardSmartspaceViewModel::dateView
+
+ val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId
+ val weatherViewId = keyguardSmartspaceViewModel.weatherId
+ val dateViewId = keyguardSmartspaceViewModel.dateId
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
@@ -67,10 +70,21 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel)
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+ KeyguardSmartspaceViewBinder.bind(
+ this,
+ constraintLayout,
+ keyguardClockViewModel,
+ keyguardSmartspaceViewModel,
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
// Generally, weather should be next to dateView
// smartspace should be below date & weather views
constraintSet.apply {
@@ -130,7 +144,20 @@ constructor(
}
}
}
- updateVisibility(constraintSet)
+ }
+ updateVisibility(constraintSet)
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+ listOf(smartspaceView, dateView, weatherView).forEach {
+ it?.let {
+ if (it.parent == constraintLayout) {
+ constraintLayout.removeView(it)
+ }
+ }
}
}
@@ -158,14 +185,4 @@ constructor(
}
}
}
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- listOf(smartspaceView, dateView, weatherView).forEach {
- it?.let {
- if (it.parent == constraintLayout) {
- constraintLayout.removeView(it)
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
new file mode 100644
index 000000000000..1302bfa6fc31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import javax.inject.Inject
+
+class SplitShadeClockSection
+@Inject
+constructor(
+ clockInteractor: KeyguardClockInteractor,
+ keyguardClockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
+ context: Context,
+ splitShadeStateController: SplitShadeStateController,
+) :
+ ClockSection(
+ clockInteractor,
+ keyguardClockViewModel,
+ smartspaceViewModel,
+ context,
+ splitShadeStateController
+ ) {
+ override fun applyDefaultConstraints(constraints: ConstraintSet) {
+ super.applyDefaultConstraints(constraints)
+ val largeClockEndGuideline =
+ if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+ else R.id.split_shade_guideline
+ constraints.apply {
+ connect(
+ R.id.lockscreen_clock_view_large,
+ ConstraintSet.END,
+ largeClockEndGuideline,
+ ConstraintSet.END
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index b0b5c81dd11c..0f8e67340cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -72,25 +71,9 @@ constructor(
return
}
constraintSet.apply {
- val bottomMargin =
- context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
- if (migrateClocksToBlueprint()) {
- connect(
- R.id.nssl_placeholder,
- TOP,
- smartspaceViewModel.smartspaceViewId,
- TOP,
- bottomMargin
- )
- setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
- } else {
- val splitShadeTopMargin =
- context.resources.getDimensionPixelSize(
- R.dimen.large_screen_shade_header_height
- )
- connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
- }
+ val splitShadeTopMargin =
+ context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+ connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
connect(R.id.nssl_placeholder, START, PARENT_ID, START)
connect(R.id.nssl_placeholder, END, PARENT_ID, END)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index e18893a381f6..fa4de044623d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -20,12 +20,12 @@ import android.content.Context
import android.hardware.biometrics.SensorLocationInternal
import com.android.settingslib.Utils
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -43,7 +43,9 @@ constructor(
val context: Context,
configurationInteractor: ConfigurationInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
fingerprintPropertyRepository: FingerprintPropertyRepository,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
val iconLocation: Flow<IconLocation> =
@@ -72,11 +74,7 @@ constructor(
.onStart {
emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
}
- private val fgIconPadding: Flow<Int> =
- configurationInteractor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
+ private val fgIconPadding: Flow<Int> = udfpsOverlayInteractor.iconPadding
val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
combine(
fgIconColor,
@@ -90,26 +88,8 @@ constructor(
)
}
- private val bgColor: Flow<Int> =
- configurationInteractor.onAnyConfigurationChange
- .map {
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
- }
- .onStart {
- emit(
- Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.colorSurface
- )
- )
- }
- val bgViewModel: Flow<DeviceEntryBackgroundViewModel.BackgroundViewModel> =
- bgColor.map { color ->
- DeviceEntryBackgroundViewModel.BackgroundViewModel(
- alpha = 1f,
- tint = color,
- )
- }
+ val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
+ val bgAlpha: Flow<Float> = flowOf(1f)
data class IconLocation(
val left: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index c45caf0b18fd..be9ae1db79e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -19,11 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import com.android.settingslib.Utils
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -34,7 +33,7 @@ class DeviceEntryBackgroundViewModel
@Inject
constructor(
val context: Context,
- configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor
+ configurationInteractor: ConfigurationInteractor,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -44,8 +43,8 @@ constructor(
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
) {
- private val color: Flow<Int> =
- configurationRepository.onAnyConfigurationChange
+ val color: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
.map {
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
}
@@ -57,7 +56,7 @@ constructor(
)
)
}
- private val alpha: Flow<Float> =
+ val alpha: Flow<Float> =
setOf(
lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
@@ -69,17 +68,4 @@ constructor(
alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
-
- val viewModel: Flow<BackgroundViewModel> =
- combine(color, alpha) { color, alpha ->
- BackgroundViewModel(
- alpha = alpha,
- tint = color,
- )
- }
-
- data class BackgroundViewModel(
- val alpha: Float,
- val tint: Int,
- )
}
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 3aeff61c15e7..528a2eebc9cd 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
@@ -27,8 +27,8 @@ 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.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
@@ -79,10 +79,10 @@ constructor(
?: false
)
- val clockShouldBeCentered: Flow<Boolean> =
+ val clockShouldBeCentered: StateFlow<Boolean> =
keyguardInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = true
+ initialValue = false
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index 4541458892bb..26e7ee8f561b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import android.util.Log
+import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
@@ -25,7 +26,7 @@ import javax.inject.Inject
@SysUISingleton
class KeyguardSmartspaceViewModel
@Inject
-constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) {
+constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) {
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
@@ -38,6 +39,10 @@ constructor(val context: Context, smartspaceController: LockscreenSmartspaceCont
val weatherId: Int
get() = getId("weather_smartspace_view")
+ var smartspaceView: View? = null
+ var weatherView: View? = null
+ var dateView: View? = null
+
private fun getId(name: String): Int {
return context.resources.getIdentifier(name, "id", context.packageName).also {
if (it == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569ca7c8..36bbe4e49415 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@ class LockscreenContentViewModel
constructor(
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
+ val longPress: KeyguardLongPressViewModel,
) {
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
deleted file mode 100644
index 6e77e13e8513..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.content.Context
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** View-model for UDFPS AOD view. */
-@ExperimentalCoroutinesApi
-class UdfpsAodViewModel
-@Inject
-constructor(
- val interactor: UdfpsKeyguardInteractor,
- val context: Context,
-) {
- val alpha: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
- val isVisible: Flow<Boolean> = alpha.map { it != 0f }
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
deleted file mode 100644
index 098b481491de..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.graphics.Rect
-import com.android.systemui.biometrics.UdfpsKeyguardView
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class UdfpsKeyguardViewModels
-@Inject
-constructor(
- private val viewModel: UdfpsKeyguardViewModel,
- private val internalViewModel: UdfpsKeyguardInternalViewModel,
- private val aodViewModel: UdfpsAodViewModel,
- private val lockscreenFingerprintViewModel: FingerprintViewModel,
- private val lockscreenBackgroundViewModel: BackgroundViewModel,
-) {
-
- fun setSensorBounds(sensorBounds: Rect) {
- viewModel.sensorBounds = sensorBounds
- }
-
- fun bindViews(view: UdfpsKeyguardView) {
- UdfpsKeyguardViewBinder.bind(
- view,
- viewModel,
- internalViewModel,
- aodViewModel,
- lockscreenFingerprintViewModel,
- lockscreenBackgroundViewModel
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
deleted file mode 100644
index 642904df21b7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.content.Context
-import androidx.annotation.ColorInt
-import com.android.settingslib.Utils.getColorAttrDefaultColor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.Offsets
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.res.R
-import com.android.wm.shell.animation.Interpolators
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-/** View-model for UDFPS lockscreen views. */
-@ExperimentalCoroutinesApi
-open class UdfpsLockscreenViewModel(
- context: Context,
- lockscreenColorResId: Int,
- alternateBouncerColorResId: Int,
- transitionInteractor: KeyguardTransitionInteractor,
- udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) {
- private val toLockscreen: Flow<TransitionViewModel> =
- transitionInteractor.anyStateToLockscreenTransition.map {
- TransitionViewModel(
- alpha =
- if (it.from == KeyguardState.AOD) {
- it.value // animate
- } else {
- 1f
- },
- scale = 1f,
- color = getColorAttrDefaultColor(context, lockscreenColorResId),
- )
- }
-
- private val toAlternateBouncer: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- transitionInteractor.transitionStepsToState(KeyguardState.ALTERNATE_BOUNCER).map {
- TransitionViewModel(
- alpha = 1f,
- scale =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f
- } else {
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
- },
- color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
- )
- }
- }
-
- private val fadeOut: Flow<TransitionViewModel> =
- keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
- merge(
- transitionInteractor.anyStateToGoneTransition,
- transitionInteractor.anyStateToAodTransition,
- transitionInteractor.anyStateToOccludedTransition,
- transitionInteractor.anyStateToPrimaryBouncerTransition,
- transitionInteractor.anyStateToDreamingTransition,
- )
- .map {
- TransitionViewModel(
- alpha =
- if (visibleInKeyguardState(it.from, statusBarState)) {
- 1f - it.value
- } else {
- 0f
- },
- scale = 1f,
- color =
- if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
- getColorAttrDefaultColor(context, alternateBouncerColorResId)
- } else {
- getColorAttrDefaultColor(context, lockscreenColorResId)
- },
- )
- }
- }
-
- private fun visibleInKeyguardState(
- state: KeyguardState,
- statusBarState: StatusBarState
- ): Boolean {
- return when (state) {
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.AOD,
- KeyguardState.PRIMARY_BOUNCER,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED -> false
- KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
- KeyguardState.ALTERNATE_BOUNCER -> true
- }
- }
-
- private val keyguardStateTransition =
- merge(
- toAlternateBouncer,
- toLockscreen,
- fadeOut,
- )
-
- private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
- udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
- if (hideAffordances) {
- 0f
- } else {
- 1f
- }
- }
-
- private val alphaMultiplier: Flow<Float> =
- combine(
- transitionInteractor.startedKeyguardState,
- dialogHideAffordancesAlphaMultiplier,
- udfpsKeyguardInteractor.shadeExpansion,
- udfpsKeyguardInteractor.qsProgress,
- ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
- ->
- if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
- 1f
- } else {
- dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
- }
- }
-
- val transition: Flow<TransitionViewModel> =
- combine(
- alphaMultiplier,
- keyguardStateTransition,
- ) { alphaMultiplier, keyguardStateTransition ->
- TransitionViewModel(
- alpha = keyguardStateTransition.alpha * alphaMultiplier,
- scale = keyguardStateTransition.scale,
- color = keyguardStateTransition.color,
- )
- }
- val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
-}
-
-@ExperimentalCoroutinesApi
-class FingerprintViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- android.R.attr.textColorPrimary,
- com.android.internal.R.attr.materialColorOnPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- ) {
- val dozeAmount: Flow<Float> = interactor.dozeAmount
- val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets
-
- // Padding between the fingerprint icon and its bounding box in pixels.
- val padding: Flow<Int> =
- interactor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
-}
-
-@ExperimentalCoroutinesApi
-class BackgroundViewModel
-@Inject
-constructor(
- val context: Context,
- transitionInteractor: KeyguardTransitionInteractor,
- interactor: UdfpsKeyguardInteractor,
- keyguardInteractor: KeyguardInteractor,
-) :
- UdfpsLockscreenViewModel(
- context,
- com.android.internal.R.attr.colorSurface,
- com.android.internal.R.attr.materialColorPrimaryFixed,
- transitionInteractor,
- interactor,
- keyguardInteractor,
- )
-
-data class TransitionViewModel(
- val alpha: Float,
- val scale: Float,
- @ColorInt val color: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d8bb3e65392f..0d5ba641b599 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -26,6 +26,7 @@ import com.android.systemui.log.echo.LogcatEchoTrackerDebug;
import com.android.systemui.log.echo.LogcatEchoTrackerProd;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -417,6 +418,18 @@ public class LogModule {
}
/**
+ * Provides a {@link ClockMessageBuffers} which contains the keyguard clock message buffers.
+ */
+ @Provides
+ public static ClockMessageBuffers provideKeyguardClockMessageBuffers(
+ @KeyguardClockLog LogBuffer infraClockLog,
+ @KeyguardSmallClockLog LogBuffer smallClockLog,
+ @KeyguardLargeClockLog LogBuffer largeClockLog
+ ) {
+ return new ClockMessageBuffers(infraClockLog, smallClockLog, largeClockLog);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 724241d8d41f..185a78369a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.pipeline
import android.content.Context
+import android.content.pm.UserInfo
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.KeepForWeakReference
@@ -88,7 +89,11 @@ constructor(
private val userTrackerCallback =
object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
- handleUserSwitched(newUser)
+ handleUserSwitched()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ handleProfileChanged()
}
}
@@ -109,7 +114,10 @@ constructor(
}
allEntries.put(key, data)
- if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
+ if (
+ !lockscreenUserManager.isCurrentProfile(data.userId) ||
+ !lockscreenUserManager.isProfileAvailable(data.userId)
+ ) {
return
}
@@ -231,7 +239,20 @@ constructor(
}
@VisibleForTesting
- internal fun handleUserSwitched(id: Int) {
+ internal fun handleProfileChanged() {
+ // TODO(b/317221348) re-add media removed when profile is available.
+ allEntries.forEach { (key, data) ->
+ if (!lockscreenUserManager.isProfileAvailable(data.userId)) {
+ // Only remove media when the profile is unavailable.
+ if (DEBUG) Log.d(TAG, "Removing $key after profile change")
+ userEntries.remove(key, data)
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun handleUserSwitched() {
// If the user changes, remove all current MediaData objects and inform listeners
val listenersCopy = listeners
val keyCopy = userEntries.keys.toMutableList()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1ec099d..3e6d46c00df9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@ constructor(
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = checkNotNull(routeInfo.clientPackageName)
+ val packageName = routeInfo.clientPackageName
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@ constructor(
}
val icon =
MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
index 3c501277ab8c..2408af192463 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -17,23 +17,22 @@
package com.android.systemui.mediaprojection.taskswitcher
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.pssTaskSwitcher
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import dagger.Lazy
import javax.inject.Inject
@SysUISingleton
class MediaProjectionTaskSwitcherCoreStartable
@Inject
constructor(
- private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
- private val featureFlags: FeatureFlags,
+ private val notificationCoordinatorLazy: Lazy<TaskSwitcherNotificationCoordinator>,
) : CoreStartable {
override fun start() {
- if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
- notificationCoordinator.start()
+ if (pssTaskSwitcher()) {
+ notificationCoordinatorLazy.get().start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 270bfbe4274d..3aa9daac4866 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -37,7 +37,7 @@ import android.os.UserManager
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.VisibleForTesting
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1534653bc1dd..958ace358816 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -48,12 +48,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -62,7 +63,10 @@ import java.util.concurrent.Future;
import javax.inject.Inject;
@SysUISingleton
-public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
+public class PowerUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -223,7 +227,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 7184fa0685af..8dd0ea0aa2af 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,14 +16,19 @@
package com.android.systemui.power.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
import com.android.systemui.power.data.repository.PowerRepositoryModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the power package. */
@@ -33,6 +38,17 @@ import dagger.Module;
}
)
public interface PowerModule {
+ /** Starts PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI.class)
+ CoreStartable bindPowerUIStartable(PowerUI impl);
+
+ /** Listen to config changes for PowerUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindPowerUIConfigChanges(PowerUI impl);
+
/** */
@Binds
EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index 2345667f0409..83b6f0d40aff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -19,16 +19,21 @@ import android.os.IBinder;
import android.service.quicksettings.IQSTileService;
import android.util.Log;
+import androidx.annotation.NonNull;
+
public class QSTileServiceWrapper {
private static final String TAG = "IQSTileServiceWrapper";
+ @NonNull
private final IQSTileService mService;
- public QSTileServiceWrapper(IQSTileService service) {
+ public QSTileServiceWrapper(@NonNull IQSTileService service) {
mService = service;
}
+ // This can never be null, as it's the constructor parameter and it's final
+ @NonNull
public IBinder asBinder() {
return mService.asBinder();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index e08eb37b79ba..880289e88813 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -40,6 +40,7 @@ import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -54,8 +55,10 @@ import dagger.assisted.AssistedInject;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
/**
* Manages the lifecycle of a TileService.
@@ -101,8 +104,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements
private final ActivityManager mActivityManager;
private Set<Integer> mQueuedMessages = new ArraySet<>();
- @Nullable
- private volatile QSTileServiceWrapper mWrapper;
+ @NonNull
+ private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty();
private boolean mListening;
private IBinder mClickBinder;
@@ -222,6 +225,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
// Only try a new binding if we are not currently bound.
mIsBound.compareAndSet(false, bindServices());
if (!mIsBound.get()) {
+ Log.d(TAG, "Failed to bind to service");
mContext.unbindService(this);
}
} catch (SecurityException e) {
@@ -281,7 +285,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
service.linkToDeath(this, 0);
} catch (RemoteException e) {
}
- mWrapper = wrapper;
+ mOptionalWrapper = Optional.of(wrapper);
handlePendingMessages();
}
@@ -368,6 +372,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements
* are supposed to be bound, we will try to bind after some amount of time.
*/
private void handleDeath() {
+ if (!mIsBound.get()) {
+ // If we are already not bound, don't do anything else.
+ return;
+ }
mExecutor.execute(() -> {
if (!mIsBound.get()) {
// If we are already not bound, don't do anything else.
@@ -522,7 +530,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Override
public void onTileAdded() {
if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
- if (mWrapper == null || !mWrapper.onTileAdded()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) {
queueMessage(MSG_ON_ADDED);
handleDeath();
}
@@ -531,7 +539,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Override
public void onTileRemoved() {
if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
- if (mWrapper == null || !mWrapper.onTileRemoved()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) {
queueMessage(MSG_ON_REMOVED);
handleDeath();
}
@@ -541,7 +549,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
public void onStartListening() {
if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
mListening = true;
- if (mWrapper != null && !mWrapper.onStartListening()) {
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) {
handleDeath();
}
}
@@ -550,7 +558,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
public void onStopListening() {
if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
mListening = false;
- if (mWrapper != null && !mWrapper.onStopListening()) {
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
handleDeath();
}
}
@@ -558,7 +566,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Override
public void onClick(IBinder iBinder) {
if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
- if (mWrapper == null || !mWrapper.onClick(iBinder)) {
+ if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) {
mClickBinder = iBinder;
queueMessage(MSG_ON_CLICK);
handleDeath();
@@ -568,7 +576,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Override
public void onUnlockComplete() {
if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
- if (mWrapper == null || !mWrapper.onUnlockComplete()) {
+ if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) {
queueMessage(MSG_ON_UNLOCK_COMPLETE);
handleDeath();
}
@@ -577,7 +585,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Nullable
@Override
public IBinder asBinder() {
- return mWrapper != null ? mWrapper.asBinder() : null;
+ return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null);
}
@Override
@@ -591,18 +599,42 @@ public class TileLifecycleManager extends BroadcastReceiver implements
}
private void freeWrapper() {
- if (mWrapper != null) {
+ if (mOptionalWrapper.isPresent()) {
try {
- mWrapper.asBinder().unlinkToDeath(this, 0);
+ mOptionalWrapper.ifPresent(
+ (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0)
+ );
} catch (NoSuchElementException e) {
Log.w(TAG, "Trying to unlink not linked recipient for component"
+ mIntent.getComponent().flattenToShortString());
}
- mWrapper = null;
+ mOptionalWrapper = Optional.empty();
}
}
public interface TileChangeListener {
void onTileChanged(ComponentName tile);
}
+
+ /**
+ * Returns true if the Optional is empty OR performing the action on the content of the Optional
+ * (when not empty) fails.
+ */
+ private static boolean isNullOrFailedAction(
+ Optional<QSTileServiceWrapper> optionalWrapper,
+ Predicate<QSTileServiceWrapper> action
+ ) {
+ return !optionalWrapper.map(action::test).orElse(false);
+ }
+
+ /**
+ * Returns true if the Optional is not empty AND performing the action on the content of
+ * the Optional fails.
+ */
+ private static boolean isNotNullAndFailedAction(
+ Optional<QSTileServiceWrapper> optionalWrapper,
+ Predicate<QSTileServiceWrapper> action
+ ) {
+ return !optionalWrapper.map(action::test).orElse(true);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 66da8bdd0311..216d716b07a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,9 +42,7 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -63,8 +61,7 @@ constructor(
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val sysuiDialogFactory: SystemUIDialog.Factory,
- private val userContextProvider: UserContextProvider,
+ private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -102,7 +99,8 @@ constructor(
private fun showPrompt(view: View?) {
val dialog: AlertDialog =
- RecordIssueDialogDelegate(sysuiDialogFactory, userContextProvider) {
+ delegateFactory
+ .create {
isRecording = true
refreshState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 4b21e447ebf5..f07162377358 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -29,11 +29,12 @@ import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
@@ -57,7 +58,10 @@ import javax.inject.Inject;
*/
@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
@SysUISingleton
-public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+public class RearDisplayDialogController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private int[] mFoldedStates;
private boolean mStartedFolded;
@@ -96,7 +100,7 @@ public class RearDisplayDialogController implements CoreStartable, CommandQueue.
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
&& mDialogViewContainer != null) {
// Refresh the dialog view when configuration is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
new file mode 100644
index 000000000000..6ab294dd9818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.reardisplay
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface RearDisplayModule {
+
+ /** Start RearDisplayDialogController. */
+ @Binds
+ @IntoMap
+ @ClassKey(RearDisplayDialogController::class)
+ abstract fun bindRearDisplayDialogControllerStartable(
+ impl: RearDisplayDialogController
+ ): CoreStartable
+
+ /** Listen to config changes for RearDisplayDialogController. */
+ @Binds
+ @IntoSet
+ fun bindRearDisplayDialogControllerConfigChanges(
+ impl: RearDisplayDialogController
+ ): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f957d771..4ee65b90fb67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -23,13 +23,17 @@ import android.provider.Settings;
import com.android.systemui.CoreStartable;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
/**
* A proxy to a Recents implementation.
*/
-public class Recents implements CoreStartable, CommandQueue.Callbacks {
+public class Recents implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private final Context mContext;
private final RecentsImplementation mImpl;
@@ -53,7 +57,7 @@ public class Recents implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
mImpl.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
index 77a4b9f72cd8..11089173a708 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -18,14 +18,17 @@ package com.android.systemui.recents;
import android.content.Context;
-import com.android.systemui.res.R;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/**
* Dagger injection module for {@link RecentsImplementation}
@@ -33,6 +36,28 @@ import dagger.multibindings.IntoMap;
@Module
public abstract class RecentsModule {
+ /** Start Recents. */
+ @Binds
+ @IntoMap
+ @ClassKey(Recents.class)
+ abstract CoreStartable bindRecentsStartable(Recents impl);
+
+ /** Listen to config changes for Recents. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindRecentsConfigChanges(Recents impl);
+
+ /** Start ScreenPinningRequest. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenPinningRequest.class)
+ abstract CoreStartable bindScreenPinningRequestStartable(ScreenPinningRequest impl);
+
+ /** Listen to config changes for ScreenPinningRequest. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindScreenPinningRequestConfigChanges(ScreenPinningRequest impl);
+
/**
* @return The {@link RecentsImplementation} from the config.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3e574e788494..2b717cb32533 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -54,25 +54,29 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.leak.RotationUtils;
+import dagger.Lazy;
+
import java.util.ArrayList;
import javax.inject.Inject;
-import dagger.Lazy;
-
@SysUISingleton
-public class ScreenPinningRequest implements View.OnClickListener,
- NavigationModeController.ModeChangedListener, CoreStartable {
+public class ScreenPinningRequest implements
+ View.OnClickListener,
+ NavigationModeController.ModeChangedListener,
+ CoreStartable,
+ ConfigurationController.ConfigurationListener {
private static final String TAG = "ScreenPinningRequest";
private final Context mContext;
@@ -149,7 +153,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRequestWindow != null) {
mRequestWindow.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 5bf44fa5b143..e051df4e6c7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -24,27 +24,63 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
+import android.os.UserHandle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.Button
import android.widget.PopupMenu
import android.widget.Switch
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.screenrecord.ScreenRecordingAudioSource
import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
-class RecordIssueDialogDelegate(
+class RecordIssueDialogDelegate
+@AssistedInject
+constructor(
private val factory: SystemUIDialog.Factory,
private val userContextProvider: UserContextProvider,
- private val onStarted: Runnable
+ private val userTracker: UserTracker,
+ private val flags: FeatureFlagsClassic,
+ @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
+ private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ private val userFileManager: UserFileManager,
+ @Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
+ /** To inject dependencies and allow for easier testing */
+ @AssistedFactory
+ interface Factory {
+ /** Create a dialog object */
+ fun create(onStarted: Runnable): RecordIssueDialogDelegate
+ }
+
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
private lateinit var issueTypeButton: Button
+ @MainThread
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
dialog.apply {
setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
@@ -63,17 +99,64 @@ class RecordIssueDialogDelegate(
override fun createDialog(): SystemUIDialog = factory.create(this)
+ @MainThread
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
dialog.apply {
window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
window?.setGravity(Gravity.CENTER)
screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+ screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
+ onScreenRecordSwitchClicked(context, isEnabled)
+ }
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
}
}
+ @AnyThread
+ private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
+ if (!isEnabled) return
+
+ bgExecutor.execute {
+ if (
+ flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+ devicePolicyResolver
+ .get()
+ .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+ ) {
+ mainExecutor.execute {
+ ScreenCaptureDisabledDialog(context).show()
+ screenRecordSwitch.isChecked = false
+ }
+ return@execute
+ }
+
+ mediaProjectionMetricsLogger.notifyProjectionInitiated(
+ userTracker.userId,
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+ )
+
+ if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+ val prefs =
+ userFileManager.getSharedPreferences(
+ RecordIssueTile.TILE_SPEC,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+ if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+ mainExecutor.execute {
+ ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+ setOnCancelListener { screenRecordSwitch.isChecked = false }
+ show()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @MainThread
private fun onIssueTypeClicked(context: Context) {
val selectedCategory = issueTypeButton.text.toString()
val popupMenu = PopupMenu(context, issueTypeButton)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
new file mode 100644
index 000000000000..de6d3f698285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
+
+class ScreenCapturePermissionDialogDelegate(
+ private val dialogFactory: SystemUIDialog.Factory,
+ private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ setIcon(R.drawable.ic_screenrecord)
+ setTitle(R.string.screenrecord_permission_dialog_title)
+ setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen)
+ setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() }
+ setPositiveButton(R.string.slice_permission_allow) { _, _ ->
+ sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply()
+ dismiss()
+ }
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
+ }
+ }
+
+ override fun createDialog(): SystemUIDialog = dialogFactory.create(this)
+}
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 47518bbee57a..5abb4dde856b 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
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabl
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
import com.android.systemui.util.println
+import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -80,8 +81,8 @@ constructor(
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val powerInteractor: PowerInteractor,
- private val simBouncerInteractor: SimBouncerInteractor,
- private val authenticationInteractor: AuthenticationInteractor,
+ private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
+ private val authenticationInteractor: Lazy<AuthenticationInteractor>,
) : CoreStartable {
override fun start() {
@@ -152,7 +153,7 @@ constructor(
}
}
applicationScope.launch {
- simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked ->
+ simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked ->
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
val isUnlocked = deviceEntryInteractor.isUnlocked.value
@@ -166,15 +167,17 @@ constructor(
isUnlocked && canSwipeToEnter == false -> {
switchToScene(
targetSceneKey = SceneKey.Gone,
- loggingReason = "All SIM cards unlocked and device already" +
- " unlocked and lockscreen doesn't require a swipe to dismiss."
+ loggingReason =
+ "All SIM cards unlocked and device already" +
+ " unlocked and lockscreen doesn't require a swipe to dismiss."
)
}
else -> {
switchToScene(
targetSceneKey = SceneKey.Lockscreen,
- loggingReason = "All SIM cards unlocked and device still locked" +
- " or lockscreen still requires a swipe to dismiss."
+ loggingReason =
+ "All SIM cards unlocked and device still locked" +
+ " or lockscreen still requires a swipe to dismiss."
)
}
}
@@ -262,7 +265,7 @@ constructor(
" to swipe up on lockscreen to enter.",
)
} else if (
- authenticationInteractor.getAuthenticationMethod() ==
+ authenticationInteractor.get().getAuthenticationMethod() ==
AuthenticationMethodModel.Sim
) {
switchToScene(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index a950539e84aa..bee315261f89 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -29,7 +29,7 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.WindowManager
import android.view.WindowManagerGlobal
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index f56f41635006..3081f89d0cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,7 +20,7 @@ import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 713ede69edec..86f652389b42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 38d00f707b6d..238a552604ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -18,7 +18,7 @@ package com.android.systemui.screenshot
import android.media.MediaPlayer
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.async
+import com.android.app.tracing.coroutines.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.google.errorprone.annotations.CanIgnoreReturnValue
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index f6c25e0a0f2c..e464fd0c8e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,7 +5,7 @@ import android.os.Trace
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 9f416bbf2c59..f2fa0ef3f30f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -135,6 +135,10 @@ open class UserTrackerImpl internal constructor(
val filter = IntentFilter().apply {
addAction(Intent.ACTION_LOCALE_CHANGED)
addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_PROFILE_ADDED)
+ addAction(Intent.ACTION_PROFILE_REMOVED)
+ addAction(Intent.ACTION_PROFILE_AVAILABLE)
+ addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -157,7 +161,11 @@ open class UserTrackerImpl internal constructor(
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_ADDED,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
- Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+ Intent.ACTION_PROFILE_ADDED,
+ Intent.ACTION_PROFILE_REMOVED,
+ Intent.ACTION_PROFILE_AVAILABLE,
+ Intent.ACTION_PROFILE_UNAVAILABLE -> {
handleProfilesChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d13edf01cc4a..d382b7ae2bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,6 +89,17 @@ public class BrightnessDialog extends Activity {
if (mShadeInteractor.isQsExpanded().getValue()) {
finish();
}
+
+ View view = findViewById(R.id.brightness_mirror_container);
+ if (view != null) {
+ collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
+ }
+ }
+
+ private void onShadeStateChange(boolean isQsExpanded) {
+ if (isQsExpanded) {
+ requestFinish();
+ }
}
private void setWindowAttributes() {
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 bc5090f14d23..be1fa2bcadf9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
mListener.onChanged(mTracking, progress, false);
SeekableSliderEventProducer eventProducer =
mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
+ if (eventProducer != null && fromUser) {
eventProducer.onProgressChanged(seekBar, progress, fromUser);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 878e6faf32e7..17eb3c83fefe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1720,9 +1720,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
// To prevent the weather clock from overlapping with the notification shelf on AOD, we use
// the small clock here
- if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
- && hasVisibleNotifications() && isOnAod()) {
- return SMALL;
+ // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
+ if (!migrateClocksToBlueprint()) {
+ if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
+ && hasVisibleNotifications() && isOnAod()) {
+ return SMALL;
+ }
}
return LARGE;
}
@@ -1742,8 +1745,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
} else {
layout = mNotificationContainerParent;
}
+
if (migrateClocksToBlueprint()) {
- mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+ mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
} else {
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 93c55de7d480..71efbab0f738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -44,6 +44,13 @@ public interface NotificationLockscreenUserManager {
boolean isCurrentProfile(int userId);
+ /**
+ *
+ * @param userId user Id
+ * @return true if user profile is running.
+ */
+ boolean isProfileAvailable(int userId);
+
/** Adds a listener to be notified when the current user changes. */
void addUserChangedListener(UserChangedListener listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 05c383910542..633510d9c438 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -461,6 +461,13 @@ public class NotificationLockscreenUserManagerImpl implements
}
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isProfileAvailable(int userId) {
+ synchronized (mLock) {
+ return mUserManager.isUserRunning(userId);
+ }
+ }
+
private void setShowLockscreenNotifications(boolean show) {
mShowLockscreenNotifications = show;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 3e9c6fbb2ec4..3b48b3922dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -3,10 +3,8 @@ package com.android.systemui.statusbar.notification
import android.util.FloatProperty
import android.view.View
import androidx.annotation.FloatRange
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import kotlin.math.abs
@@ -42,13 +40,13 @@ interface Roundable {
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
val topCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.topCornerRadius
else topRoundness * maxRadius
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
val bottomCornerRadius: Float
get() =
- if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
+ if (NotificationsImprovedHunAnimation.isEnabled) roundableState.bottomCornerRadius
else bottomRoundness * maxRadius
/** Get and update the current radii */
@@ -318,13 +316,10 @@ constructor(
internal val targetView: View,
private val roundable: Roundable,
maxRadius: Float,
- featureFlags: FeatureFlags? = null
) {
internal var maxRadius = maxRadius
private set
- internal val newHeadsUpAnim = RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS, featureFlags)
-
/** Animatable for top roundness */
private val topAnimatable = topAnimatable(roundable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index 30e2f0e0a57f..921556854440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -37,10 +38,12 @@ class NotificationIconsInteractor
constructor(
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val bubbles: Optional<Bubbles>,
+ private val headsUpNotificationIconInteractor: HeadsUpNotificationIconInteractor,
private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
) {
/** Returns a subset of all active notifications based on the supplied filtration parameters. */
fun filteredNotifSet(
+ forceShowHeadsUp: Boolean = false,
showAmbient: Boolean = true,
showLowPriority: Boolean = true,
showDismissed: Boolean = true,
@@ -49,18 +52,21 @@ constructor(
): Flow<Set<ActiveNotificationModel>> {
return combine(
activeNotificationsInteractor.topLevelRepresentativeNotifications,
+ headsUpNotificationIconInteractor.isolatedNotification,
keyguardViewStateRepository.areNotificationsFullyHidden,
- ) { notifications, notifsFullyHidden ->
+ ) { notifications, isolatedNotifKey, notifsFullyHidden ->
notifications
.asSequence()
.filter { model: ActiveNotificationModel ->
shouldShowNotificationIcon(
model = model,
+ forceShowHeadsUp = forceShowHeadsUp,
showAmbient = showAmbient,
showLowPriority = showLowPriority,
showDismissed = showDismissed,
showRepliedMessages = showRepliedMessages,
showPulsing = showPulsing,
+ isolatedNotifKey = isolatedNotifKey,
notifsFullyHidden = notifsFullyHidden,
)
}
@@ -70,14 +76,17 @@ constructor(
private fun shouldShowNotificationIcon(
model: ActiveNotificationModel,
+ forceShowHeadsUp: Boolean,
showAmbient: Boolean,
showLowPriority: Boolean,
showDismissed: Boolean,
showRepliedMessages: Boolean,
showPulsing: Boolean,
+ isolatedNotifKey: String?,
notifsFullyHidden: Boolean,
): Boolean {
return when {
+ forceShowHeadsUp && model.key == isolatedNotifKey -> true
!showAmbient && model.isAmbient -> false
!showLowPriority && model.isSilent -> false
!showDismissed && model.isRowDismissed -> false
@@ -118,6 +127,7 @@ constructor(
val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
iconsInteractor.filteredNotifSet(
+ forceShowHeadsUp = true,
showAmbient = false,
showLowPriority = showSilentIcons,
showDismissed = false,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 8fe00225463b..b76cdb8a5a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -237,7 +237,7 @@ object NotificationIconContainerViewBinder {
// Add and bind.
val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
- for ((idx, notifKey) in toAdd.withIndex()) {
+ for (notifKey in toAdd) {
// Lookup the StatusBarIconView from the store.
val sbiv = viewStore.iconView(notifKey)
if (sbiv == null) {
@@ -256,7 +256,7 @@ object NotificationIconContainerViewBinder {
// added again.
removeTransientView(sbiv)
}
- view.addView(sbiv, idx)
+ view.addView(sbiv)
boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
boundViewsByNotifKey[notifKey] =
Pair(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 6e5ac470f1b6..d00cd1fcfed9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -96,8 +96,8 @@ constructor(
iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
}
}
- .pairwise(initialValue = null)
.distinctUntilChanged()
+ .pairwise(initialValue = null)
.sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
val animate =
when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 4fe05ec9990c..fca527f5fc4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,7 +31,6 @@ import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
@@ -44,6 +43,7 @@ import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.util.DumpUtilsKt;
@@ -67,7 +67,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
* The content of the view should start showing at animation progress value of
* #ALPHA_APPEAR_START_FRACTION.
*/
- private static final float ALPHA_APPEAR_START_FRACTION = .4f;
+
+ private static final float ALPHA_APPEAR_START_FRACTION = .7f;
/**
* The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION
* The start of the animation is at #ALPHA_APPEAR_START_FRACTION
@@ -86,9 +87,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
*/
private boolean mActivated;
- private final Interpolator mSlowOutFastInInterpolator;
private Interpolator mCurrentAppearInterpolator;
-
NotificationBackgroundView mBackgroundNormal;
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
@@ -116,7 +115,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
- mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
updateColors();
@@ -400,12 +398,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
targetValue = 1.0f;
} else {
- mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
+ mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE;
targetValue = 0.0f;
}
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
- mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAppearAnimator.setInterpolator(mCurrentAppearInterpolator);
+ } else {
+ mAppearAnimator.setInterpolator(Interpolators.LINEAR);
+ }
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
mAppearAnimator.addUpdateListener(animation -> {
@@ -502,8 +504,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private void updateAppearRect() {
- float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
- mAppearAnimationFraction);
+ float interpolatedFraction =
+ NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction
+ : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
final int actualHeight = getActualHeight();
float bottom = actualHeight * interpolatedFraction;
@@ -524,6 +527,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
private float getInterpolatedAppearAnimationFraction() {
+
if (mAppearAnimationFraction >= 0) {
return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
}
@@ -569,7 +573,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public float getTopCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getTopCornerRadius();
}
@@ -579,7 +583,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public float getBottomCornerRadius() {
- if (mImprovedHunAnimation.isEnabled()) {
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
return super.getBottomCornerRadius();
}
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 5872840913f4..31ca106d2bc9 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
@@ -513,15 +513,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AnimationDrawable) {
- AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+ if (drawable instanceof AnimationDrawable animationDrawable) {
if (running) {
animationDrawable.start();
} else {
animationDrawable.stop();
}
- } else if (drawable instanceof AnimatedVectorDrawable) {
- AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
+ } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) {
if (running) {
animationDrawable.start();
} else {
@@ -3439,8 +3437,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected boolean childNeedsClipping(View child) {
- if (child instanceof NotificationContentView) {
- NotificationContentView contentView = (NotificationContentView) child;
+ if (child instanceof NotificationContentView contentView) {
if (isClippingNeeded()) {
return true;
} else if (hasRoundedCorner()
@@ -3522,8 +3519,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void applyToView(View view) {
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isExpandAnimationRunning()) {
return;
}
@@ -3543,8 +3539,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected void onYTranslationAnimationFinished(View view) {
super.onYTranslationAnimationFinished(view);
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isHeadsUpAnimatingAway()) {
row.setHeadsUpAnimatingAway(false);
}
@@ -3553,8 +3548,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void animateTo(View child, AnimationProperties properties) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.isExpandAnimationRunning()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2a3e69b7f4d4..aefd34817fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -28,10 +28,9 @@ import android.util.IndentingPrintWriter;
import android.view.View;
import android.view.ViewOutlineProvider;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.util.DumpUtilsKt;
@@ -49,8 +48,6 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private float mOutlineAlpha = -1f;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
- protected final RefactorFlag mImprovedHunAnimation =
- RefactorFlag.forView(Flags.IMPROVED_HUN_ANIMATIONS);
/**
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -126,7 +123,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
return EMPTY_PATH;
}
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
+ if (!NotificationsImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
float overShoot = topRadius + bottomRadius - height;
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 49674d603509..c4d266ed2f17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -676,8 +676,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
mViewState.headsUpIsVisible = false;
// handling reset for child notifications
- if (this instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) this;
+ if (this instanceof ExpandableNotificationRow row) {
List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (row.isSummaryWithChildren() && children != null) {
for (ExpandableNotificationRow childRow : children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
new file mode 100644
index 000000000000..16d35fe9fa68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsImprovedHunAnimation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications improved hun animation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsImprovedHunAnimation {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_IMPROVED_HUN_ANIMATION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsImprovedHunAnimation()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0236fc265add..45b9c269b61c 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
@@ -869,8 +869,7 @@ public class NotificationChildrenContainer extends ViewGroup
Path clipPath = mChildClipPath;
if (clipPath != null) {
final float translation;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow notificationRow) {
translation = notificationRow.getTranslation();
} else {
translation = child.getTranslationX();
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 85e63e58bc7a..0f640c9c2608 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
@@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -992,8 +993,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE
- && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ && child instanceof ExpandableNotificationRow row) {
if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
&& row.getProvider().shouldShowGutsOnSnapOpen()) {
top = Math.min(top, row.getTranslationY());
@@ -1128,10 +1128,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
for (int i = 0; i < n; i++) {
View view = getChildAt(i);
if (view.getVisibility() == View.GONE
- || !(view instanceof ExpandableNotificationRow)) {
+ || !(view instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
boolean beforeSpeedBump;
if (mHighPriorityBeforeSpeedBump) {
@@ -1768,16 +1767,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public static boolean isPinnedHeadsUp(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
return row.isHeadsUp() && row.isPinned();
}
return false;
}
private boolean isHeadsUp(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
return row.isHeadsUp();
}
return false;
@@ -1819,8 +1816,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
&& touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
- if (slidingChild instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+ if (slidingChild instanceof ExpandableNotificationRow row) {
NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
&& mTopHeadsUpEntry.getRow() != row
@@ -2363,8 +2359,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
float rowTranslation = child.getTranslationY();
if (rowTranslation >= translationY) {
return child;
- } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ } else if (!ignoreChildren && child instanceof ExpandableNotificationRow row) {
if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
List<ExpandableNotificationRow> notificationChildren =
row.getAttachedChildren();
@@ -2885,8 +2880,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void focusNextViewIfFocused(View view) {
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.shouldRefocusOnDismiss()) {
View nextView = row.getChildAfterViewWhenDismissed();
if (nextView == null) {
@@ -3034,8 +3028,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private int getIntrinsicHeight(View view) {
- if (view instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) view;
+ if (view instanceof ExpandableView expandableView) {
return expandableView.getIntrinsicHeight();
}
return view.getHeight();
@@ -3125,8 +3118,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
}
@@ -3195,8 +3187,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void updateAnimationState(boolean running, View child) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setAnimationRunning(running);
}
}
@@ -3323,8 +3314,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
logHunAnimationSkipped(row, "row has no viewState");
continue;
}
+ boolean shouldHunAppearFromTheBottom =
+ mStackScrollAlgorithm.shouldHunAppearFromBottom(mAmbientState, viewState);
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
- if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
+ if (pinnedAndClosed || shouldHunAppearFromTheBottom) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
} else {
@@ -3336,6 +3329,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // TODO(b/283084712) remove this with the flag and update the HUN filters at
+ // creation
+ event.filter.animateHeight = false;
+ }
mAnimationEvents.add(event);
if (SPEW) {
Log.v(TAG, "Generating HUN animation event: "
@@ -3350,11 +3348,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAddedHeadsUpChildren.clear();
}
- private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
- return viewState.getYTranslation() + viewState.height
- >= mAmbientState.getMaxHeadsUpTranslation();
- }
-
private void generateGroupExpansionEvent() {
// Generate a group expansion/collapsing event if there is such a group at all
if (mExpandedGroupView != null) {
@@ -3391,8 +3384,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// we need to know the view after this one
float removedTranslation = child.getTranslationY();
boolean ignoreChildren = true;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
removedTranslation = row.getTranslationWhenRemoved();
ignoreChildren = false;
@@ -3434,8 +3426,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void generatePositionChangeEvents() {
for (ExpandableView child : mChildrenChangingPositions) {
Integer duration = null;
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
if (row.getEntry().isMarkedForUserTriggeredMovement()) {
duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE;
row.getEntry().markForUserTriggeredMovement(false);
@@ -4119,8 +4110,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void clearUserLockedViews() {
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = getChildAtIndex(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setUserLocked(false);
}
}
@@ -4134,8 +4124,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
);
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = getChildAtIndex(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
clearTemporaryViewsInGroup(
/* viewGroup = */ row.getChildrenContainer(),
/* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())"
@@ -4220,8 +4209,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
void updateChronometerForChild(View child) {
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
row.setChronometerRunning(mIsExpanded);
}
}
@@ -4260,8 +4248,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
- if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row && !onKeyguard()) {
// TODO: once we're recycling this will need to check the adapter position of the child
if (row.isUserLocked() && row != getFirstChildNotGone()) {
if (row.isSummaryWithChildren()) {
@@ -4320,8 +4307,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void clearHeadsUpDisappearRunning() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
row.setHeadsUpAnimatingAway(false);
if (row.isSummaryWithChildren()) {
for (ExpandableNotificationRow child : row.getAttachedChildren()) {
@@ -4918,7 +4904,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+ mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
requestChildrenUpdate();
}
@@ -5203,8 +5191,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
View swipedView = mSwipeHelper.getSwipedView();
pw.println("Swiped view: " + swipedView);
- if (swipedView instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) swipedView;
+ if (swipedView instanceof ExpandableView expandableView) {
expandableView.dump(pw, args);
}
});
@@ -5287,8 +5274,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
return true;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (isVisible(row) && includeChildInClearAll(row, selection)) {
return true;
}
@@ -5314,9 +5300,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (shouldHideParent(view, selection)) {
viewsToHide.add(view);
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-
+ if (view instanceof ExpandableNotificationRow parent) {
if (isChildrenVisible(parent)) {
for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
if (isVisible(child) && includeChildInClearAll(child, selection)) {
@@ -5336,10 +5320,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
+ if (!(view instanceof ExpandableNotificationRow parent)) {
continue;
}
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
if (includeChildInClearAll(parent, selection)) {
viewsToRemove.add(parent);
}
@@ -5978,8 +5961,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
mSwipeHelper.forceResetSwipeState(child);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow childRow) {
List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren();
if (grandchildren != null) {
for (ExpandableNotificationRow grandchild : grandchildren) {
@@ -6265,8 +6247,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
static boolean canChildBeDismissed(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
return false;
}
@@ -6276,8 +6257,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
static boolean canChildBeCleared(View v) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
return false;
}
@@ -6372,8 +6352,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/* Only ever called as a consequence of an expansion gesture in the shade. */
@Override
public void setUserExpandedChild(View v, boolean userExpanded) {
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
if (userExpanded && onKeyguard()) {
// Due to a race when locking the screen while touching, a notification may be
// expanded even after we went back to keyguard. An example of this happens if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7c7d9431e0fc..6f5058c8c52e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -405,8 +405,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
if (!mAllowLongPress) {
return;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
.setType(MetricsEvent.TYPE_ACTION)
@@ -426,8 +425,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Override
public void onMenuShown(View row) {
- if (row instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
+ if (row instanceof ExpandableNotificationRow notificationRow) {
mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
.setType(MetricsEvent.TYPE_ACTION));
@@ -492,10 +490,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
@Override
public void onChildDismissed(View view) {
- if (!(view instanceof ActivatableNotificationView)) {
+ if (!(view instanceof ActivatableNotificationView row)) {
return;
}
- ActivatableNotificationView row = (ActivatableNotificationView) view;
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
@@ -519,8 +516,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
if (mView.getClearAllInProgress()) {
return;
}
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (view instanceof ExpandableNotificationRow row) {
if (row.isHeadsUp()) {
mHeadsUpManager.addSwipedOutNotification(
row.getEntry().getSbn().getKey());
@@ -551,8 +547,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
ev.getY(),
true /* requireMinHeight */,
false /* ignoreDecors */);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow row) {
ExpandableNotificationRow parent = row.getNotificationParent();
if (parent != null && parent.areChildrenExpanded()
&& (parent.areGutsExposed()
@@ -582,8 +577,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mView.onSwipeEnd();
- if (animView instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
+ if (animView instanceof ExpandableNotificationRow row) {
if (row.isPinned() && !canChildBeDismissed(row)
&& row.getEntry().getSbn().getNotification().fullScreenIntent
== null) {
@@ -1980,8 +1974,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
// Check if we need to clear any snooze leavebehinds
if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
- && guts.getGutsContent() instanceof NotificationSnooze) {
- NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
+ && guts.getGutsContent() instanceof NotificationSnooze ns) {
if ((ns.isExpanded() && isCancelOrUp)
|| (!horizontalSwipeWantsIt && scrollerWantsIt)) {
// If the leavebehind is expanded we clear it on the next up event, otherwise we
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 06ca9a50bb6d..664a6b6c54c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.List;
@@ -66,12 +67,15 @@ public class StackScrollAlgorithm {
private boolean mClipNotificationScrollToTop;
@VisibleForTesting
float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
private float mQuickQsOffsetHeight;
private float mSmallCornerRadius;
private float mLargeCornerRadius;
+ private int mHeadsUpAppearHeightBottom;
public StackScrollAlgorithm(
Context context,
@@ -94,6 +98,8 @@ public class StackScrollAlgorithm {
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
+ mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize(
+ R.dimen.heads_up_appear_y_above_screen);
mPinnedZTranslationExtra = res.getDimensionPixelSize(
R.dimen.heads_up_pinned_elevation);
mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
@@ -221,6 +227,25 @@ public class StackScrollAlgorithm {
return getExpansionFractionWithoutShelf(mTempAlgorithmState, ambientState);
}
+ public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+ }
+
+ /**
+ * If the QuickSettings is showing full screen, we want to animate the HeadsUp Notifications
+ * from the bottom of the screen.
+ *
+ * @param ambientState Current ambient state.
+ * @param viewState The state of the HUN that is being queried to appear from the bottom.
+ *
+ * @return true if the HeadsUp Notifications should appear from the bottom
+ */
+ public boolean shouldHunAppearFromBottom(AmbientState ambientState,
+ ExpandableViewState viewState) {
+ return viewState.getYTranslation() + viewState.height
+ >= ambientState.getMaxHeadsUpTranslation();
+ }
+
public static void log(String s) {
if (DEBUG) {
android.util.Log.i(TAG, s);
@@ -229,8 +254,7 @@ public class StackScrollAlgorithm {
public static void logView(View view, String s) {
String viewString = "";
- if (view instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = ((ExpandableNotificationRow) view);
+ if (view instanceof ExpandableNotificationRow row) {
if (row.getEntry() == null) {
viewString = "ExpandableNotificationRow has null NotificationEntry";
} else {
@@ -264,8 +288,7 @@ public class StackScrollAlgorithm {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
row.updateChildrenStates();
}
}
@@ -376,8 +399,7 @@ public class StackScrollAlgorithm {
continue;
}
notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v);
- if (v instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof ExpandableNotificationRow row) {
// handle the notGoneIndex for the children as well
List<ExpandableNotificationRow> children = row.getAttachedChildren();
@@ -508,10 +530,9 @@ public class StackScrollAlgorithm {
private boolean hasNonClearableNotifs(StackScrollAlgorithmState algorithmState) {
for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.canViewBeCleared()) {
return true;
}
@@ -715,10 +736,9 @@ public class StackScrollAlgorithm {
ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!row.showingPulsing() || (i == 0 && ambientState.isPulseExpanding())) {
continue;
}
@@ -760,10 +780,9 @@ public class StackScrollAlgorithm {
ExpandableNotificationRow topHeadsUpEntry = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
- if (!(child instanceof ExpandableNotificationRow)) {
+ if (!(child instanceof ExpandableNotificationRow row)) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (!(row.isHeadsUp() || row.isHeadsUpAnimatingAway())) {
continue;
}
@@ -793,10 +812,16 @@ public class StackScrollAlgorithm {
}
}
if (row.isPinned()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ // Make sure row yTranslation is at the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(headsUpTranslation);
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
childState.hidden = false;
ExpandableViewState topState =
@@ -819,10 +844,22 @@ public class StackScrollAlgorithm {
}
}
if (row.isHeadsUpAnimatingAway()) {
- // Make sure row yTranslation is at maximum the HUN yTranslation,
- // which accounts for AmbientState.stackTopMargin in split-shade.
- childState.setYTranslation(
- Math.max(childState.getYTranslation(), headsUpTranslation));
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ if (shouldHunAppearFromBottom(ambientState, childState)) {
+ // move to the bottom of the screen
+ childState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // move to the top of the screen
+ childState.setYTranslation(-ambientState.getStackTopMargin()
+ - mHeadsUpAppearStartAboveScreen);
+ }
+ } else {
+ // Make sure row yTranslation is at maximum the HUN yTranslation,
+ // which accounts for AmbientState.stackTopMargin in split-shade.
+ childState.setYTranslation(
+ Math.max(childState.getYTranslation(), headsUpTranslation));
+ }
// keep it visible for the animation
childState.hidden = false;
}
@@ -897,8 +934,7 @@ public class StackScrollAlgorithm {
}
protected int getMaxAllowedChildHeight(View child) {
- if (child instanceof ExpandableView) {
- ExpandableView expandableView = (ExpandableView) child;
+ if (child instanceof ExpandableView expandableView) {
return expandableView.getIntrinsicHeight();
}
return child == null ? mCollapsedSize : child.getHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e94258f416ac..a3e09417b34c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK;
@@ -26,6 +27,7 @@ import android.util.Property;
import android.view.View;
import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -33,6 +35,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import java.util.ArrayList;
import java.util.HashSet;
@@ -68,6 +71,8 @@ public class StackStateAnimator {
private final int mGoToFullShadeAppearingTranslation;
private final int mPulsingAppearingTranslation;
+ @VisibleForTesting
+ float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
@@ -85,21 +90,23 @@ public class StackStateAnimator {
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
private int mHeadsUpAppearHeightBottom;
+ private int mStackTopMargin;
private boolean mShadeExpanded;
private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
private NotificationShelf mShelf;
- private float mStatusBarIconLocation;
- private int[] mTmpLocation = new int[2];
private StackStateLogger mLogger;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
+ // TODO(b/317061579) reload on configuration changes
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
mPulsingAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.pulsing_notification_appear_translation);
+ mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -455,8 +462,37 @@ public class StackStateAnimator {
.AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
row.prepareExpansionChanged();
- } else if (event.animationType == NotificationStackScrollLayout
- .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ } else if (NotificationsImprovedHunAnimation.isEnabled()
+ && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) {
+ mHeadsUpAppearChildren.add(changingView);
+
+ mTmpState.copyFrom(changingView.getViewState());
+ if (event.headsUpFromBottom) {
+ // start from the bottom of the screen
+ mTmpState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // start from the top of the screen
+ mTmpState.setYTranslation(
+ -mStackTopMargin - mHeadsUpAppearStartAboveScreen);
+ }
+ // set the height and the initial position
+ mTmpState.applyToView(changingView);
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN);
+
+ Runnable onAnimationEnd = null;
+ if (loggable) {
+ // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with
+ // normal ADD animations, which would not be logged here.
+ String finalKey = key;
+ mLogger.logHUNViewAppearing(key);
+ onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+ }
+ changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
+ /* isHeadsUpAppear= */ true, onAnimationEnd);
+ } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ NotificationsImprovedHunAnimation.assertInLegacyMode();
// This item is added, initialize its properties.
ExpandableViewState viewState = changingView.getViewState();
mTmpState.copyFrom(viewState);
@@ -536,6 +572,10 @@ public class StackStateAnimator {
changingView.setInRemovalAnimation(true);
};
}
+ if (NotificationsImprovedHunAnimation.isEnabled()) {
+ mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+ Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+ }
long removeAnimationDelay = changingView.performRemoveAnimation(
ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
0, 0.0f, true /* isHeadsUpAppear */,
@@ -601,6 +641,10 @@ public class StackStateAnimator {
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
+ public void setStackTopMargin(int stackTopMargin) {
+ mStackTopMargin = stackTopMargin;
+ }
+
public void setShadeExpanded(boolean shadeExpanded) {
mShadeExpanded = shadeExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 97cb45aebd13..6e8ad2e50620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -21,13 +21,17 @@ import android.graphics.Rect
import android.os.LocaleList
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import javax.inject.Inject
@SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController {
+class ConfigurationControllerImpl @Inject constructor(
+ @Application context: Context,
+ ) : ConfigurationController {
- private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+ private val listeners: MutableList<ConfigurationListener> = ArrayList()
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
@@ -143,14 +147,12 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
}
}
-
-
- override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun addCallback(listener: ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
}
- override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun removeCallback(listener: ConfigurationListener) {
listeners.remove(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
new file mode 100644
index 000000000000..90ebaf269a39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@SysUISingleton
+class ConfigurationControllerStartable
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+) : CoreStartable {
+ override fun start() {
+ listeners.forEach { configurationController.addCallback(it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
deleted file mode 100644
index 7048a78f31fc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.systemui.statusbar.phone;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
- */
-public class StatusBarIconHolder {
- public static final int TYPE_ICON = 0;
- /**
- * TODO (b/249790733): address this once the new pipeline is in place
- * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
- * to inform the old view system about changes to the data set (the list of mobile icons). The
- * design of the new pipeline should allow for removal of this icon holder type, and obsolete
- * the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_MOBILE_NEW = 3;
-
- /**
- * TODO (b/238425913): address this once the new pipeline is in place
- * This type exists so that the new wifi pipeline can be used to inform the old view system
- * about the existence of the wifi icon. The design of the new pipeline should allow for removal
- * of this icon holder type, and obsolete the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_WIFI_NEW = 4;
-
- @IntDef({
- TYPE_ICON,
- TYPE_MOBILE_NEW,
- TYPE_WIFI_NEW
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface IconType {}
-
- private StatusBarIcon mIcon;
- private @IconType int mType = TYPE_ICON;
- private int mTag = 0;
-
- /** Returns a human-readable string representing the given type. */
- public static String getTypeString(@IconType int type) {
- switch(type) {
- case TYPE_ICON: return "ICON";
- case TYPE_MOBILE_NEW: return "MOBILE_NEW";
- case TYPE_WIFI_NEW: return "WIFI_NEW";
- default: return "UNKNOWN";
- }
- }
-
- private StatusBarIconHolder() {
- }
-
- public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
- StatusBarIconHolder wrapper = new StatusBarIconHolder();
- wrapper.mIcon = icon;
-
- return wrapper;
- }
-
- /** Creates a new holder with for the new wifi icon. */
- public static StatusBarIconHolder forNewWifiIcon() {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_WIFI_NEW;
- return holder;
- }
-
- /**
- * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
- * determine icon ordering and building the correct view model
- */
- public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_MOBILE_NEW;
- holder.mTag = subId;
-
- return holder;
- }
-
- /**
- * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
- */
- public static StatusBarIconHolder fromCallIndicatorState(
- Context context,
- CallIndicatorIconState state) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
- String contentDescription = state.isNoCalling
- ? state.noCallingDescription : state.callStrengthDescription;
- holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, resId), 0, 0, contentDescription);
- holder.mTag = state.subId;
- return holder;
- }
-
- public @IconType int getType() {
- return mType;
- }
-
- @Nullable
- public StatusBarIcon getIcon() {
- return mIcon;
- }
-
- public void setIcon(StatusBarIcon icon) {
- mIcon = icon;
- }
-
- public boolean isVisible() {
- switch (mType) {
- case TYPE_ICON:
- return mIcon.visible;
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // this is effectively an unused return value.
- return true;
- default:
- return true;
- }
- }
-
- public void setVisible(boolean visible) {
- if (isVisible() == visible) {
- return;
- }
-
- switch (mType) {
- case TYPE_ICON:
- mIcon.visible = visible;
- break;
-
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // ignore setVisible.
- break;
- }
- }
-
- public int getTag() {
- return mTag;
- }
-
- @Override
- public String toString() {
- return "StatusBarIconHolder(type=" + getTypeString(mType)
- + " tag=" + getTag()
- + " visible=" + isVisible() + ")";
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
new file mode 100644
index 000000000000..5b55a1e73dc3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.systemui.statusbar.phone
+
+import android.annotation.IntDef
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+
+/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
+class StatusBarIconHolder private constructor() {
+ @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+ @Retention(AnnotationRetention.SOURCE)
+ internal annotation class IconType
+
+ var icon: StatusBarIcon? = null
+
+ @IconType
+ var type = TYPE_ICON
+ private set
+
+ var tag = 0
+ private set
+
+ var isVisible: Boolean
+ get() =
+ when (type) {
+ TYPE_ICON -> icon!!.visible
+
+ // The new pipeline controls visibilities via the view model and
+ // view binder, so
+ // this is effectively an unused return value.
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> true
+ else -> true
+ }
+ set(visible) {
+ if (isVisible == visible) {
+ return
+ }
+ when (type) {
+ TYPE_ICON -> icon!!.visible = visible
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> {}
+ }
+ }
+
+ override fun toString(): String {
+ return ("StatusBarIconHolder(type=${getTypeString(type)}" +
+ " tag=$tag" +
+ " visible=$isVisible)")
+ }
+
+ companion object {
+ const val TYPE_ICON = 0
+
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place This type exists so
+ * that the new pipeline (see [MobileIconViewModel]) can be used to inform the old view
+ * system about changes to the data set (the list of mobile icons). The design of the new
+ * pipeline should allow for removal of this icon holder type, and obsolete the need for
+ * this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_MOBILE_NEW = 3
+
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place This type exists so
+ * that the new wifi pipeline can be used to inform the old view system about the existence
+ * of the wifi icon. The design of the new pipeline should allow for removal of this icon
+ * holder type, and obsolete the need for this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_WIFI_NEW = 4
+
+ /** Returns a human-readable string representing the given type. */
+ fun getTypeString(@IconType type: Int): String {
+ return when (type) {
+ TYPE_ICON -> "ICON"
+ TYPE_MOBILE_NEW -> "MOBILE_NEW"
+ TYPE_WIFI_NEW -> "WIFI_NEW"
+ else -> "UNKNOWN"
+ }
+ }
+
+ @JvmStatic
+ fun fromIcon(icon: StatusBarIcon?): StatusBarIconHolder {
+ val wrapper = StatusBarIconHolder()
+ wrapper.icon = icon
+ return wrapper
+ }
+
+ /** Creates a new holder with for the new wifi icon. */
+ @JvmStatic
+ fun forNewWifiIcon(): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_WIFI_NEW
+ return holder
+ }
+
+ /**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ @JvmStatic
+ fun fromSubIdForModernMobileIcon(subId: Int): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_MOBILE_NEW
+ holder.tag = subId
+ return holder
+ }
+
+ /** Creates a new StatusBarIconHolder from a CallIndicatorIconState. */
+ @JvmStatic
+ fun fromCallIndicatorState(
+ context: Context,
+ state: CallIndicatorIconState,
+ ): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ val resId = if (state.isNoCalling) state.noCallingResId else state.callStrengthResId
+ val contentDescription =
+ if (state.isNoCalling) state.noCallingDescription else state.callStrengthDescription
+ holder.icon =
+ StatusBarIcon(
+ UserHandle.SYSTEM,
+ context.packageName,
+ Icon.createWithResource(context, resId),
+ 0,
+ 0,
+ contentDescription,
+ )
+ holder.tag = state.subId
+ return holder
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 942d186e7005..b048da492eb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.phone.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
+import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger Module providing {@link CentralSurfacesImpl}.
@@ -34,4 +38,12 @@ public interface StatusBarPhoneModule {
@Binds
@SysUISingleton
CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
+
+ /**
+ * Starts {@link ConfigurationControllerStartable}
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(ConfigurationControllerStartable.class)
+ CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2740cc6682a6..11456ffaa4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -453,11 +453,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public void initNotificationIconArea() {
ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
if (NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaInner =
- LayoutInflater.from(getContext())
- .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
NotificationIconContainer notificationIcons =
notificationIconArea.requireViewById(R.id.notificationIcons);
+ mNotificationIconAreaInner = notificationIcons;
mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
} else {
mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 425da5fc30ba..48bf7ac60ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -48,7 +49,7 @@ class FullMobileConnectionRepository(
override val subId: Int,
startingIsCarrierMerged: Boolean,
override val tableLogBuffer: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
private val defaultNetworkName: NetworkNameModel,
private val networkNameSeparator: String,
@Application scope: CoroutineScope,
@@ -331,7 +332,7 @@ class FullMobileConnectionRepository(
fun build(
subId: Int,
startingIsCarrierMerged: Boolean,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 4fb99c24c8ca..be2c21be816d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -96,7 +96,7 @@ import kotlinx.coroutines.withContext
class MobileConnectionRepositoryImpl(
override val subId: Int,
private val context: Context,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
connectivityManager: ConnectivityManager,
@@ -448,7 +448,7 @@ class MobileConnectionRepositoryImpl(
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
- subscriptionModel: StateFlow<SubscriptionModel?>,
+ subscriptionModel: Flow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
): MobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2a510e41ec9c..a455db2e67ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -357,10 +357,10 @@ constructor(
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
- private fun subscriptionModelForSubId(subId: Int): StateFlow<SubscriptionModel?> {
- return subscriptions
- .map { list -> list.firstOrNull { model -> model.subscriptionId == subId } }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ private fun subscriptionModelForSubId(subId: Int): Flow<SubscriptionModel?> {
+ return subscriptions.map { list ->
+ list.firstOrNull { model -> model.subscriptionId == subId }
+ }
}
private fun createRepositoryForSubId(subId: Int): FullMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 4864fb8ca634..5bced934be7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -303,8 +303,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
&& editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
- // Pass null to ensure all inputs are cleared for this entry b/227115380
- mController.removeRemoteInput(mEntry, null,
+ mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");
}
}
@@ -536,6 +535,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
return;
}
+ // RemoteInputView can be detached from window before IME close event in some cases like
+ // remote input view removal with notification update. As a result of this, RemoteInputView
+ // will stop ime animation updates, which results in never removing remote input. That's why
+ // we have to set mRemoteEditImeAnimatingAway false on detach to remove remote input.
+ mEntry.mRemoteEditImeAnimatingAway = false;
mController.removeRemoteInput(mEntry, mToken,
/* reason= */"RemoteInputView#onDetachedFromWindow");
mController.removeSpinning(mEntry.getKey(), mToken);
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
new file mode 100644
index 000000000000..6cd999320236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.toast
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ToastModule {
+ /** Starts ToastUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(ToastUI::class)
+ fun bindToastUIStartable(service: ToastUI): CoreStartable
+
+ /** Listen to config changes for ToastUI. */
+ @Binds @IntoSet fun bindToastUIConfigChanges(service: ToastUI): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 27f8121e6e66..85a455d23d49 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -42,6 +42,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.util.Objects;
@@ -51,7 +52,10 @@ import javax.inject.Inject;
* Controls display of text toasts.
*/
@SysUISingleton
-public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
+public class ToastUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
// values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
@@ -187,7 +191,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (newConfig.orientation != mOrientation) {
mOrientation = newConfig.orientation;
if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 472f0ae364c5..cf6b0d9d1bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -5,9 +5,7 @@ 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.dagger.qualifiers.Tracing
-import com.android.systemui.Flags.coroutineTracing
-import com.android.app.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
-import com.android.app.tracing.TraceContextElement
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
@@ -16,7 +14,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.plus
import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
/** Providers for various coroutines-related constructs. */
@Module
@@ -83,9 +80,6 @@ class CoroutinesModule {
@Tracing
@SysUISingleton
fun tracingCoroutineContext(): CoroutineContext {
- return if (coroutineTracing()) {
- coroutineTracingIsEnabled = true
- TraceContextElement()
- } else EmptyCoroutineContext
+ return createCoroutineTracingContext()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 3451ae0a9be3..dc2b80c5e391 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -22,16 +22,17 @@ import android.os.Handler;
import android.util.Log;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import javax.inject.Inject;
@SysUISingleton
-public class VolumeUI implements CoreStartable {
+public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -60,7 +61,7 @@ public class VolumeUI implements CoreStartable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (!mEnabled) return;
mVolumeComponent.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 53217d481599..8d06a8f3afd8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@ import android.media.AudioManager;
import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
@@ -36,15 +37,30 @@ import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import com.android.systemui.volume.VolumeUI;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the volume package. */
@Module
public interface VolumeModule {
+ /** Starts VolumeUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumeUI.class)
+ CoreStartable bindVolumeUIStartable(VolumeUI impl);
+
+ /** Listen to config changes for VolumeUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
+
/** */
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6f58bc2fcc9f..e6637e60b146 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -30,13 +30,15 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
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.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockTickRate
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -94,9 +96,9 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
private lateinit var repository: FakeKeyguardRepository
- @Mock private lateinit var smallLogBuffer: LogBuffer
- @Mock private lateinit var largeLogBuffer: LogBuffer
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+ private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
private lateinit var underTest: ClockEventController
@Mock private lateinit var zenModeController: ZenModeController
@@ -140,8 +142,7 @@ class ClockEventControllerTest : SysuiTestCase() {
context,
mainExecutor,
bgExecutor,
- smallLogBuffer,
- largeLogBuffer,
+ clockBuffers,
withDeps.featureFlags,
zenModeController
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 639276e302a7..b3eab8a5b79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -248,8 +248,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigChanged(Configuration newConfig) {
+ super.onConfigChanged(newConfig);
mExecutor.runAllReady();
}
@@ -892,7 +892,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
}
@@ -913,7 +913,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
verify(mDotViewController, times(2)).setShowingListener(null);
@@ -949,7 +949,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Only top windows should be added.
verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
@@ -976,7 +976,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Both top and bottom windows should be added with VISIBLE because of privacy dot and
// cutout, but rounded corners visibility shall be gone because of no rounding.
@@ -1013,7 +1013,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
- mScreenDecorations.onConfigurationChanged(null);
+ mScreenDecorations.onConfigChanged(null);
assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
@@ -1145,7 +1145,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertThat(mScreenDecorations.mIsRegistered, is(false));
doReturn(true).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1156,7 +1156,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mScreenDecorations.start();
assertThat(mScreenDecorations.mIsRegistered, is(true));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1168,7 +1168,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertThat(mScreenDecorations.mIsRegistered, is(true));
doReturn(false).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(false));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index f8856c968a23..bd49927c8b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -102,8 +102,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
getContext().getMainThreadHandler(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
- mContext.getSystemService(DisplayManager.class));
+ mMagnification.mWindowMagnificationControllerSupplier =
+ new FakeWindowMagnificationControllerSupplier(
+ mContext.getSystemService(DisplayManager.class));
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class));
@@ -201,10 +202,10 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
}
- private class FakeControllerSupplier extends
+ private class FakeWindowMagnificationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
- FakeControllerSupplier(DisplayManager displayManager) {
+ FakeWindowMagnificationControllerSupplier(DisplayManager displayManager) {
super(displayManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index d0e1678739c4..3b5cbea079a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -124,7 +124,7 @@ public class MagnificationTest extends SysuiTestCase {
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
@@ -325,9 +325,9 @@ public class MagnificationTest extends SysuiTestCase {
@Test
public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
final WindowMagnificationController mController = mock(WindowMagnificationController.class);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mController);
- mMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
+ mMagnification.mWindowMagnificationControllerSupplier.get(TEST_DISPLAY);
mOverviewProxyListener.onConnectionChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e38a00f..9bcab57bec87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
- private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private DismissView mDismissView;
@Rule
@@ -65,19 +65,20 @@ public class DismissAnimationControllerTest extends SysuiTestCase {
stubMenuViewAppearance);
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
}
@Test
public void showDismissView_success() {
- mDismissAnimationController.showDismissView(true);
+ mDragToInteractAnimationController.showDismissView(true);
verify(mDismissView).show();
}
@Test
public void hideDismissView_success() {
- mDismissAnimationController.showDismissView(false);
+ mDragToInteractAnimationController.showDismissView(false);
verify(mDismissView).hide();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 7f12c05ad28a..9c8de302c5e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -62,7 +62,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Mock
private SecureSettings mSecureSettings;
@Mock
- private DismissAnimationController.DismissCallback mStubDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
private MenuView mMenuView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 9797f2ab45db..e1522f5f6751 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -68,7 +68,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
- private DismissAnimationController mDismissAnimationController;
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
@@ -92,10 +92,10 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mStubMenuView, stubMenuViewAppearance));
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController =
- spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mDragToInteractAnimationController =
+ spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index e2aa984de46b..647dae6fd6c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,10 +20,9 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import org.junit.Assert.assertFalse
import org.junit.Before
@@ -42,8 +41,7 @@ class UdfpsBpViewControllerTest : SysuiTestCase() {
@Mock lateinit var udfpsBpView: UdfpsBpView
@Mock lateinit var statusBarStateController: StatusBarStateController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
@@ -55,7 +53,7 @@ class UdfpsBpViewControllerTest : SysuiTestCase() {
UdfpsBpViewController(
udfpsBpView,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 27d93eb31922..8f0e910271c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -24,7 +24,6 @@ import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -48,7 +47,6 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
@@ -62,6 +60,7 @@ class BiometricStatusRepositoryTest : SysuiTestCase() {
@Before
fun setUp() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index 6978923879b4..d7b7d79425c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.biometrics.domain.interactor
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.content.ComponentName
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -44,7 +43,6 @@ import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
@@ -59,6 +57,7 @@ class BiometricStatusInteractorImplTest : SysuiTestCase() {
@Before
fun setup() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
biometricStatusRepository = FakeBiometricStatusRepository()
underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00ddd608..42d2c98f324c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
@@ -260,14 +261,14 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
SideFpsOverlayViewBinder(
testScope.backgroundScope,
mContext,
- biometricStatusInteractor,
- displayStateInteractor,
- deviceEntrySideFpsOverlayInteractor,
- fpsUnlockTracker,
- layoutInflater,
- sideFpsProgressBarViewModel,
- sfpsSensorInteractor,
- windowManager
+ { biometricStatusInteractor },
+ { displayStateInteractor },
+ { deviceEntrySideFpsOverlayInteractor },
+ { fpsUnlockTracker },
+ { layoutInflater },
+ { sideFpsProgressBarViewModel },
+ { sfpsSensorInteractor },
+ { windowManager }
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index fd5a584935ef..1b6aaabd4fd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -22,17 +22,13 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
-import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock
+import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.systemUIDialogManager
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -48,25 +44,14 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
- val kosmos =
+ private val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
}
- val testScope = kosmos.testScope
-
- private val testDeviceEntryIconTransitionAlpha = MutableStateFlow(0f)
- private val testDeviceEntryIconTransition: DeviceEntryIconTransition
- get() =
- object : DeviceEntryIconTransition {
- override val deviceEntryParentViewAlpha: Flow<Float> =
- testDeviceEntryIconTransitionAlpha.asStateFlow()
- }
-
- init {
- kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition)
- }
private val systemUIDialogManager = kosmos.systemUIDialogManager
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val testScope = kosmos.testScope
+ private val deviceEntryIconViewModelTransition = kosmos.fakeDeviceEntryIconViewModelTransition
private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel
@Captor
@@ -82,7 +67,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -96,7 +81,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = .3f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(.3f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -110,7 +95,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 1f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f)
runCurrent()
verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture())
@@ -124,7 +109,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() {
testScope.runTest {
val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
- testDeviceEntryIconTransitionAlpha.value = 0f
+ deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(0f)
runCurrent()
bouncerRepository.setAlternateVisible(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdcafad8..983e4b3a9922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 16b2ed633f11..9c5cd713dffb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -140,21 +140,76 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
assertThat(widgets())
.containsExactly(
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
- communalItemRankEntry2,
+ )
+ .inOrder()
+
+ // swapped priorities
+ val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1.copy(rank = 2),
+ communalWidgetItemEntry1,
+ communalItemRankEntry2.copy(rank = 1),
communalWidgetItemEntry2
)
+ .inOrder()
+ }
- val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
- communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+ @Test
+ fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
+ testScope.runTest {
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ existingWidgets.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
assertThat(widgets())
.containsExactly(
communalItemRankEntry2,
communalWidgetItemEntry2,
communalItemRankEntry1,
- communalWidgetItemEntry1
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
+
+ // map with no item in the middle at index 1
+ val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
)
+ .inOrder()
+ // add the new middle item that we left space for.
+ communalWidgetDao.addWidget(
+ widgetId = widgetInfo3.widgetId,
+ provider = widgetInfo3.provider,
+ priority = 2,
+ )
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry3.copy(rank = 2),
+ communalWidgetItemEntry3,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
}
data class FakeWidgetMetadata(
@@ -176,8 +231,15 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_2"),
priority = 2
)
+ val widgetInfo3 =
+ FakeWidgetMetadata(
+ widgetId = 3,
+ provider = ComponentName("pk_name", "cls_name_3"),
+ priority = 3
+ )
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
@@ -192,5 +254,12 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
)
+ val communalWidgetItemEntry3 =
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = widgetInfo3.widgetId,
+ componentName = widgetInfo3.provider.flattenToString(),
+ itemId = communalItemRankEntry3.uid,
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
new file mode 100644
index 000000000000..93ce86a2959e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.ui.viewmodel.udfpsAccessibilityOverlayViewModel
+import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+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.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) }
+ }
+ private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
+ private val testScope = kosmos.testScope
+ private val accessibilityRepository = kosmos.fakeAccessibilityRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private val underTest = kosmos.udfpsAccessibilityOverlayViewModel
+
+ @Test
+ fun visible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ assertThat(visible).isTrue()
+ }
+
+ @Test
+ fun touchExplorationNotEnabled_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ accessibilityRepository.isTouchExplorationEnabled.value = false
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryFgIconViewModelAod_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+
+ // AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceUnlocked_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryRepository.setUnlocked(true)
+ assertThat(visible).isFalse()
+ }
+
+ @Test
+ fun deviceEntryViewAlpha0_overlayNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.visible)
+ setupVisibleStateOnLockscreen()
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(0f)
+ assertThat(visible).isFalse()
+ }
+
+ private suspend fun setupVisibleStateOnLockscreen() {
+ // A11y enabled
+ accessibilityRepository.isTouchExplorationEnabled.value = true
+
+ // Transition alpha is 1f
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(1f)
+
+ // Listening for UDFPS
+ fingerprintPropertyRepository.supportsUdfps()
+ deviceEntryFingerprintAuthRepository.setIsRunning(true)
+ deviceEntryRepository.setUnlocked(false)
+
+ // Lockscreen
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+
+ // Shade not expanded
+ shadeRepository.qsExpansion.value = 0f
+ shadeRepository.lockscreenShadeExpansion.value = 0f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd8588c..c22d35cb214f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@ class SeekableSliderEventProducerTest : SysuiTestCase() {
assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
}
+
+ @Test
+ fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+ }
+
+ @Test
+ fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e491ad11..db0496227a38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@ class SeekableSliderTrackerTest : SysuiTestCase() {
verifyNoMoreInteractions(sliderStateListener)
}
+ @Test
+ fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ initTracker(testScheduler)
+
+ // GIVEN a progress due to an external source that lands at the middle of the slider
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ verify(sliderStateListener).onSelectAndArrow(progress)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // GIVEN a progress due to an external source that lands at the upper bookend
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving back to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // WHEN a progress is recorded due to an external source that lands at the lower bookend
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves back to IDLE and there are no haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider gets an external progress change
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves to IDLE and no haptics are played
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves to WAIT and the wait job starts.
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider changes progress programmatically at the middle
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker stays in the same state and haptics are delivered appropriately
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 70d3f81de35f..027dfa15f812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.Handler
-import android.platform.test.annotations.RequiresFlagsEnabled
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -58,7 +57,6 @@ import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@OptIn(ExperimentalCoroutinesApi::class)
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@SmallTest
@RunWith(JUnit4::class)
class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@@ -80,6 +78,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@Before
fun setup() {
+ mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
primaryBouncerInteractor =
PrimaryBouncerInteractor(
bouncerRepository,
@@ -110,6 +109,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
)
underTest =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
FakeDeviceEntryFingerprintAuthRepository(),
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 3109e761e423..ad86ee9f07d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.mockito.whenever
@@ -70,7 +71,8 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
@Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection
@Mock private lateinit var clockSection: ClockSection
@Mock private lateinit var smartspaceSection: SmartspaceSection
-
+ @Mock
+ private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -90,6 +92,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
communalTutorialIndicatorSection,
clockSection,
smartspaceSection,
+ udfpsAccessibilityOverlaySection,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index e89b61f6d44e..dc0d9c5f987e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -20,7 +20,6 @@ package com.android.systemui.keyguard.ui.view.layout.sections
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -43,7 +42,6 @@ class ClockSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
- private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
private lateinit var underTest: ClockSection
@@ -88,7 +86,6 @@ class ClockSectionTest : SysuiTestCase() {
smartspaceViewModel,
mContext,
splitShadeStateController,
- featureFlags
)
}
@@ -147,24 +144,6 @@ class ClockSectionTest : SysuiTestCase() {
assetSmallClockTop(cs, expectedSmallClockTopMargin)
}
- @Test
- fun testLargeClockShouldBeCentered() {
- underTest.setClockShouldBeCentered(true)
- val cs = ConstraintSet()
- underTest.applyDefaultConstraints(cs)
- val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
- assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
- }
-
- @Test
- fun testLargeClockShouldNotBeCentered() {
- underTest.setClockShouldBeCentered(false)
- val cs = ConstraintSet()
- underTest.applyDefaultConstraints(cs)
- val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
- assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
- }
-
private fun setLargeClock(useLargeClock: Boolean) {
whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index bff27f6910ab..740110b4fee0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -69,14 +69,13 @@ class SmartspaceSectionTest : SysuiTestCase() {
keyguardUnlockAnimationController,
)
constraintLayout = ConstraintLayout(mContext)
- whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout))
- .thenReturn(smartspaceView)
- whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout))
- .thenReturn(weatherView)
- whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout))
- .thenReturn(dateView)
+ whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView)
+ whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView)
+ whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView)
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
+ whenever(keyguardSmartspaceViewModel.smartspaceController)
+ .thenReturn(lockscreenSmartspaceController)
constraintSet = ConstraintSet()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 459a74c82da4..ee1be10607cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -24,7 +24,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
deleted file mode 100644
index 6512290bf556..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsAodViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: UdfpsAodViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
- val udfpsKeyguardInteractor =
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- )
-
- underTest =
- UdfpsAodViewModel(
- udfpsKeyguardInteractor,
- context,
- )
- }
-
- @Test
- fun alphaAndVisibleUpdates_onDozeAmountChanges() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
- val visible by collectLastValue(underTest.isVisible)
-
- keyguardRepository.setDozeAmount(0f)
- runCurrent()
- assertThat(alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- keyguardRepository.setDozeAmount(.65f)
- runCurrent()
- assertThat(alpha).isEqualTo(.65f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(.23f)
- runCurrent()
- assertThat(alpha).isEqualTo(.23f)
- assertThat(visible).isTrue()
-
- keyguardRepository.setDozeAmount(1f)
- runCurrent()
- assertThat(alpha).isEqualTo(1f)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
deleted file mode 100644
index 95b2fe554f0c..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-/** Tests UdfpsFingerprintViewModel specific flows. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsFingerprintViewModelTest : SysuiTestCase() {
- private val defaultPadding = 12
- private lateinit var underTest: FingerprintViewModel
-
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Mock private lateinit var burnInHelper: BurnInHelperWrapper
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- overrideResource(com.android.systemui.res.R.dimen.lock_icon_padding, defaultPadding)
- testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- fakeCommandQueue = FakeCommandQueue()
- bouncerRepository = FakeKeyguardBouncerRepository()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- val keyguardInteractor =
- KeyguardInteractorFactory.create(
- repository = keyguardRepository,
- )
- .keyguardInteractor
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- FingerprintViewModel(
- context,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelper,
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun paddingUpdates_onScaleForResolutionChanges() =
- testScope.runTest {
- val padding by collectLastValue(underTest.padding)
-
- configRepository.setScaleForResolution(1f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding)
-
- configRepository.setScaleForResolution(2f)
- runCurrent()
- assertThat(padding).isEqualTo(defaultPadding * 2)
-
- configRepository.setScaleForResolution(.5f)
- runCurrent()
- assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
deleted file mode 100644
index 848a94b2a5d2..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.settingslib.Utils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
-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.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.wm.shell.animation.Interpolators
-import com.google.common.collect.Range
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-/** Tests UDFPS lockscreen view model transitions. */
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class UdfpsLockscreenViewModelTest : SysuiTestCase() {
- private val lockscreenColorResId = android.R.attr.textColorPrimary
- private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
- private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
- private val alternateBouncerColor =
- Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
-
- @Mock private lateinit var dialogManager: SystemUIDialogManager
-
- private lateinit var underTest: UdfpsLockscreenViewModel
- private lateinit var testScope: TestScope
- private lateinit var transitionRepository: FakeKeyguardTransitionRepository
- private lateinit var configRepository: FakeConfigurationRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
- private lateinit var shadeRepository: FakeShadeRepository
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- transitionRepository = FakeKeyguardTransitionRepository()
- shadeRepository = FakeShadeRepository()
- KeyguardInteractorFactory.create().also {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- configRepository = it.configurationRepository
- bouncerRepository = it.bouncerRepository
- }
-
- val transitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = transitionRepository,
- keyguardInteractor = keyguardInteractor,
- )
- .keyguardTransitionInteractor
-
- underTest =
- UdfpsLockscreenViewModel(
- context,
- lockscreenColorResId,
- alternateBouncerResId,
- transitionInteractor,
- UdfpsKeyguardInteractor(
- configRepository,
- BurnInInteractor(
- context,
- burnInHelperWrapper = mock(),
- testScope.backgroundScope,
- configRepository,
- keyguardInteractor,
- ),
- keyguardInteractor,
- shadeRepository,
- dialogManager,
- ),
- keyguardInteractor,
- )
- }
-
- @Test
- fun goneToAodTransition() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: gone -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "goneToAodTransition",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenShadeLockedToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-
- // TransitionState.STARTED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
-
- // TransitionState.FINISHED: lockscreen -> AOD
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun aodToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
-
- // TransitionState.RUNNING: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: AOD -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "aodToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun lockscreenToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- fun alternateBouncerToPrimaryBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> primary bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.PRIMARY_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToPrimaryBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- fun alternateBouncerToAod() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: alternate bouncer -> aod
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.ALTERNATE_BOUNCER,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "alternateBouncerToAod",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun lockscreenToOccluded() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
-
- // TransitionState.STARTED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: lockscreen -> occluded
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.OCCLUDED,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "lockscreenToOccluded",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isFalse()
- }
-
- @Test
- fun occludedToLockscreen() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> lockscreen
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToLockscreen",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(lockscreenColor)
- assertThat(visible).isTrue()
- }
-
- @Test
- fun qsProgressChange() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // qsExpansion = 0f
- shadeRepository.setQsExpansion(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .25
- shadeRepository.setQsExpansion(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.6f)
- assertThat(visible).isEqualTo(true)
-
- // qsExpansion = .5
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
-
- // qsExpansion = 1
- shadeRepository.setQsExpansion(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun shadeExpansionChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
- givenTransitionToLockscreenFinished()
-
- // shadeExpansion = 0f
- shadeRepository.setUdfpsTransitionToFullShadeProgress(0f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .2
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.8f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = .5
- shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(.5f)
- assertThat(visible).isEqualTo(true)
-
- // shadeExpansion = 1
- shadeRepository.setUdfpsTransitionToFullShadeProgress(1f)
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(0f)
- assertThat(visible).isEqualTo(false)
- }
-
- @Test
- fun dialogHideAffordancesRequestChanged() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- givenTransitionToLockscreenFinished()
- runCurrent()
- val captor = argumentCaptor<SystemUIDialogManager.Listener>()
- Mockito.verify(dialogManager).registerListener(captor.capture())
-
- captor.value.shouldHideAffordances(true)
- assertThat(transition?.alpha).isEqualTo(0f)
-
- captor.value.shouldHideAffordances(false)
- assertThat(transition?.alpha).isEqualTo(1f)
- }
-
- @Test
- fun occludedToAlternateBouncer() =
- testScope.runTest {
- val transition by collectLastValue(underTest.transition)
- val visible by collectLastValue(underTest.visible)
-
- // TransitionState.STARTED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(0f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.RUNNING: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = .6f,
- transitionState = TransitionState.RUNNING,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale)
- .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f))
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
-
- // TransitionState.FINISHED: occluded -> alternate bouncer
- transitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.OCCLUDED,
- to = KeyguardState.ALTERNATE_BOUNCER,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ownerName = "occludedToAlternateBouncer",
- )
- )
- runCurrent()
- assertThat(transition?.alpha).isEqualTo(1f)
- assertThat(transition?.scale).isEqualTo(1f)
- assertThat(transition?.color).isEqualTo(alternateBouncerColor)
- assertThat(visible).isTrue()
- }
-
- private suspend fun givenTransitionToLockscreenFinished() {
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- testScope
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 8532ffed85fb..94b9fa4a6582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -55,6 +55,7 @@ private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
private const val USER_MAIN = 0
private const val USER_GUEST = 10
+private const val PRIVATE_PROFILE = 12
private const val PACKAGE = "PKG"
private val INSTANCE_ID = InstanceId.fakeInstanceId(123)!!
private const val APP_UID = 99
@@ -82,6 +83,7 @@ class MediaDataFilterTest : SysuiTestCase() {
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
+ private lateinit var dataPrivateProfile: MediaData
private val clock = FakeSystemClock()
@Before
@@ -115,6 +117,7 @@ class MediaDataFilterTest : SysuiTestCase() {
appUid = APP_UID
)
dataGuest = dataMain.copy(userId = USER_GUEST)
+ dataPrivateProfile = dataMain.copy(userId = PRIVATE_PROFILE)
whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
whenever(smartspaceData.isActive).thenReturn(true)
@@ -130,8 +133,19 @@ class MediaDataFilterTest : SysuiTestCase() {
private fun setUser(id: Int) {
whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isProfileAvailable(anyInt())).thenReturn(false)
whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
- mediaDataFilter.handleUserSwitched(id)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(id))).thenReturn(true)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(true)
+ mediaDataFilter.handleUserSwitched()
+ }
+
+ private fun setPrivateProfileUnavailable() {
+ whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(USER_MAIN))).thenReturn(true)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(PRIVATE_PROFILE))).thenReturn(true)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(false)
+ mediaDataFilter.handleProfileChanged()
}
@Test
@@ -206,6 +220,20 @@ class MediaDataFilterTest : SysuiTestCase() {
}
@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)
+
+ // and we change profile status
+ setPrivateProfileUnavailable()
+
+ // THEN we should add the private profile media
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ }
+
+ @Test
fun hasAnyMedia_noMediaSet_returnsFalse() {
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
index bcbf666fd302..16c92ecc2181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -18,38 +18,27 @@ package com.android.systemui.mediaprojection.taskswitcher
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_PSS_TASK_SWITCHER
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
-import com.android.systemui.util.mockito.whenever
-import org.junit.Before
+import com.android.systemui.util.mockito.mock
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
- @Mock private lateinit var flags: FeatureFlags
- @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+ private val coordinator = mock<TaskSwitcherNotificationCoordinator>()
- private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
- }
+ private val coreStartable =
+ MediaProjectionTaskSwitcherCoreStartable(notificationCoordinatorLazy = { coordinator })
@Test
fun start_flagEnabled_startsCoordinator() {
- whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+ mSetFlagsRule.enableFlags(FLAG_PSS_TASK_SWITCHER)
coreStartable.start()
@@ -58,7 +47,7 @@ class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
@Test
fun start_flagDisabled_doesNotStartCoordinator() {
- whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+ mSetFlagsRule.disableFlags(FLAG_PSS_TASK_SWITCHER)
coreStartable.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 9b61447c8de7..c7479fd50db1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,8 +30,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,9 +65,9 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
- @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
+ @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
- @Mock private lateinit var userContextProvider: UserContextProvider
private lateinit var testableLooper: TestableLooper
private lateinit var tile: RecordIssueTile
@@ -76,7 +76,8 @@ class RecordIssueTileTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(host.context).thenReturn(mContext)
- whenever(dialogFactory.create(any())).thenReturn(dialog)
+ whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
+ whenever(dialogDelegate.createDialog()).thenReturn(dialog)
testableLooper = TestableLooper.get(this)
tile =
@@ -93,8 +94,7 @@ class RecordIssueTileTest : SysuiTestCase() {
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
- dialogFactory,
- userContextProvider,
+ delegateFactory,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index c108a80d0c72..273ce85f89f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -28,8 +28,8 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -82,7 +82,7 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase {
TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
- controller.onConfigurationChanged(new Configuration());
+ controller.onConfigChanged(new Configuration());
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
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 c5d35245ba7c..7ce51aea90e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.recordissue
import android.app.Dialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.widget.Button
@@ -25,48 +28,107 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.SessionCreationSource
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import com.android.systemui.model.SysUiState
+import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class RecordIssueDialogDelegateTest : SysuiTestCase() {
+ @Mock private lateinit var flags: FeatureFlagsClassic
+ @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
+ @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
+ @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
+ @Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var sharedPreferences: SharedPreferences
+
+ @Mock private lateinit var sysuiState: SysUiState
+ @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var bgExecutor: Executor
+ @Mock private lateinit var mainExecutor: Executor
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
private lateinit var dialog: SystemUIDialog
+ private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
@Before
fun setup() {
- val dialogFactory =
- SystemUIDialog.Factory(
- context,
- mock<FeatureFlags>(),
- mock<SystemUIDialogManager>(),
- mock<SysUiState>().apply {
- whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
- },
- mock<BroadcastDispatcher>(),
- mock<DialogLaunchAnimator>()
+ MockitoAnnotations.initMocks(this)
+ whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
+ whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(userContextProvider.userContext).thenReturn(mContext)
+ whenever(
+ userFileManager.getSharedPreferences(
+ eq(RecordIssueTile.TILE_SPEC),
+ eq(Context.MODE_PRIVATE),
+ anyInt()
+ )
+ )
+ .thenReturn(sharedPreferences)
+
+ factory =
+ spy(
+ SystemUIDialog.Factory(
+ context,
+ flags,
+ systemUIDialogManager,
+ sysuiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator
+ )
)
latch = CountDownLatch(1)
dialog =
- RecordIssueDialogDelegate(dialogFactory, mock()) { latch.countDown() }.createDialog()
+ RecordIssueDialogDelegate(
+ factory,
+ userContextProvider,
+ userTracker,
+ flags,
+ bgExecutor,
+ mainExecutor,
+ dprLazy,
+ mediaProjectionMetricsLogger,
+ userFileManager,
+ ) {
+ latch.countDown()
+ }
+ .createDialog()
dialog.show()
}
@@ -91,4 +153,82 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
latch.await(1L, TimeUnit.MILLISECONDS)
}
+
+ @Test
+ fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(true)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(true)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mainExecutor).execute(mainCaptor.capture())
+ mainCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger, never())
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ assertThat(screenRecordSwitch.isChecked).isFalse()
+ }
+
+ @Test
+ fun screenCapturePermissionDialog_isShown_correctly() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(false)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(false)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+ whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false))
+ .thenReturn(false)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mainExecutor).execute(mainCaptor.capture())
+ mainCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ verify(factory).create(any<ScreenCapturePermissionDialogDelegate>())
+ }
+
+ @Test
+ fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+ .thenReturn(false)
+ whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+ .thenReturn(false)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
+
+ val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ screenRecordSwitch.isChecked = true
+
+ val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(bgExecutor).execute(bgCaptor.capture())
+ bgCaptor.value.run()
+
+ verify(mediaProjectionMetricsLogger)
+ .notifyProjectionInitiated(
+ anyInt(),
+ eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+ )
+ verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index c32d2597bd61..032ec7440923 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -177,7 +177,7 @@ class UserTrackerImplTest : SysuiTestCase() {
verify(context)
.registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
with(captor.value) {
- assertThat(countActions()).isEqualTo(7)
+ assertThat(countActions()).isEqualTo(11)
assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
@@ -185,6 +185,10 @@ class UserTrackerImplTest : SysuiTestCase() {
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 88c728fd1b66..b94e483088f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -36,8 +36,12 @@ import com.android.systemui.util.mockito.any
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 kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -59,7 +63,6 @@ class BrightnessDialogTest : SysuiTestCase() {
@Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
@Mock private lateinit var brightnessController: BrightnessController
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
- @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
@Mock private lateinit var shadeInteractor: ShadeInteractor
private val clock = FakeSystemClock()
@@ -89,7 +92,6 @@ class BrightnessDialogTest : SysuiTestCase() {
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
`when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
- whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
}
@@ -180,6 +182,22 @@ class BrightnessDialogTest : SysuiTestCase() {
assertThat(activityRule.activity.isFinishing()).isFalse()
}
+ @OptIn(FlowPreview::class)
+ @Test
+ fun testFinishOnQSExpanded() = runTest {
+ val isQSExpanded = MutableStateFlow(false)
+ `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
+ activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ isQSExpanded.value = true
+ // Observe the activity's state until is it finishing or the timeout is reached, whatever
+ // comes first. This fixes the flakiness seen when using advanceUntilIdle().
+ activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
+ assertThat(activityRule.activity.isFinishing()).isTrue()
+ }
+
class TestDialog(
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
brightnessControllerFactory: BrightnessController.Factory,
@@ -194,14 +212,14 @@ class BrightnessDialogTest : SysuiTestCase() {
accessibilityMgr,
shadeInteractor
) {
- private var finishing = false
+ var finishing = MutableStateFlow(false)
override fun isFinishing(): Boolean {
- return finishing
+ return finishing.value
}
override fun requestFinish() {
- finishing = true
+ finishing.value = true
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ee94cbbcfd79..ee27c5c9ba0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.TRANSIT_CLOCK
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -128,6 +129,7 @@ class ClockRegistryTest : SysuiTestCase() {
override fun createClock(settings: ClockSettings): ClockController =
createCallbacks[settings.clockId!!]!!(settings.clockId!!)
override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+ override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index fef262f50306..e0e8d1f51884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -26,6 +26,7 @@ import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.customization.R
+import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
import com.android.systemui.util.mockito.any
@@ -49,6 +50,9 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController =
+ createClock(ClockSettings(id, null)) as DefaultClockController
+
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DefaultClockProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
new file mode 100644
index 000000000000..50349bea390f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.notifications.data.repository
+
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.shared.settings.data.repository.FakeSecureSettingsRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class NotificationSettingsRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: NotificationSettingsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettingsRepository: FakeSecureSettingsRepository
+
+ @Before
+ fun setUp() {
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ secureSettingsRepository = FakeSecureSettingsRepository()
+
+ underTest =
+ NotificationSettingsRepository(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettingsRepository = secureSettingsRepository,
+ )
+ }
+
+ @Test
+ fun testGetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 1,
+ )
+ assertThat(showNotifs).isEqualTo(true)
+
+ secureSettingsRepository.setInt(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+ value = 0,
+ )
+ assertThat(showNotifs).isEqualTo(false)
+ }
+
+ @Test
+ fun testSetIsShowNotificationsOnLockscreenEnabled() =
+ testScope.runTest {
+ val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(true)
+ assertThat(showNotifs).isEqualTo(true)
+
+ underTest.setShowNotificationsOnLockscreenEnabled(false)
+ assertThat(showNotifs).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 2bee7b85eb98..d3febf55117b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -151,6 +152,7 @@ public class NotificationListenerTest extends SysuiTestCase {
@Test
public void testOnConnectReadStatusBarSetting() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
@@ -164,6 +166,7 @@ public class NotificationListenerTest extends SysuiTestCase {
@Test
public void testOnStatusBarIconsBehaviorChanged() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index a56fb2c515a8..7d8cf3657ba1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -1,11 +1,10 @@
package com.android.systemui.statusbar.notification
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
@@ -20,8 +19,7 @@ import org.mockito.Mockito.verify
@RunWith(JUnit4::class)
class RoundableTest : SysuiTestCase() {
private val targetView: View = mock()
- private val featureFlags = FakeFeatureFlags()
- private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
+ private val roundable = FakeRoundable(targetView = targetView)
@Test
fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -150,36 +148,36 @@ class RoundableTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radius_maxed_to_height() {
whenever(targetView.height).thenReturn(10)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(5f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_topRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 0f, SOURCE1)
assertCornerRadiiEquals(5f, 0f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_bottomRadius_maxed_to_height() {
whenever(targetView.height).thenReturn(5)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(0f, 1f, SOURCE1)
assertCornerRadiiEquals(0f, 5f)
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun getCornerRadii_radii_kept() {
whenever(targetView.height).thenReturn(100)
- featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
roundable.requestRoundness(1f, 1f, SOURCE1)
assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
@@ -193,14 +191,12 @@ class RoundableTest : SysuiTestCase() {
class FakeRoundable(
targetView: View,
radius: Float = MAX_RADIUS,
- featureFlags: FeatureFlags
) : Roundable {
override val roundableState =
RoundableState(
targetView = targetView,
roundable = this,
maxRadius = radius,
- featureFlags = featureFlags
)
override val clipHeight: Int
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 360a373711d6..47feccf4bdcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -264,6 +265,7 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
val activeNotificationListRepository: ActiveNotificationListRepository
+ val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
@@ -336,6 +338,14 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
.comparingElementsUsing(byIsLastMessageFromReply)
.doesNotContain(true)
}
+
+ @Test
+ fun filteredEntrySet_includesIsolatedIcon() =
+ testComponent.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+ assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
+ }
}
private val testIcons =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 349a35ebf798..c40401fe4d59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -399,4 +399,29 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
}
+
+ @Test
+ fun isolatedIcon_lastMessageIsFromReply_notNull() =
+ testComponent.runTest {
+ val icon: Icon = mock()
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ activeNotificationsRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon,
+ isLastMessageFromReply = true,
+ )
+ )
+ }
+ .build()
+
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index cb731082b89b..0cd834ded638 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,7 +57,6 @@ import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -568,9 +567,6 @@ public class NotificationTestHelper {
NotificationEntry entry,
@InflationFlag int extraInflationFlags)
throws Exception {
- // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
- // set, but we do not want to override an existing value that is needed by a specific test.
- mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
mContext.LAYOUT_INFLATER_SERVICE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index c8dbdc505aba..d4300f08fbac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -17,6 +17,7 @@ import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -28,8 +29,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/** Tests for {@link NotificationShelf}. */
@SmallTest
@@ -53,7 +54,6 @@ open class NotificationShelfTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
mDependency.injectTestDependency(FeatureFlags::class.java, flags)
flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
- flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS)
val root = FrameLayout(context)
shelf =
LayoutInflater.from(root.context)
@@ -72,6 +72,7 @@ open class NotificationShelfTest : SysuiTestCase() {
@Test
fun testShadeWidth_BasedOnFractionToShade() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -87,6 +88,7 @@ open class NotificationShelfTest : SysuiTestCase() {
@Test
fun testShelfIsLong_WhenNotOnLockscreen() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 08ef47765174..f266f039958f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,21 +2,28 @@ package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.res.R
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.RoundableState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Expect
@@ -24,6 +31,7 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -37,22 +45,26 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
- @JvmField @Rule
- var expect: Expect = Expect.create()
+ @JvmField @Rule var expect: Expect = Expect.create()
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
+ private val notificationEntry = mock<NotificationEntry>()
private val dumpManager = mock<DumpManager>()
+ @OptIn(ExperimentalCoroutinesApi::class)
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
- private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
- layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
- }
- private val footerView = FooterView(context, /*attrs=*/null)
- private val ambientState = AmbientState(
+ private val emptyShadeView =
+ EmptyShadeView(context, /* attrs= */ null).apply {
+ layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
+ }
+ private val footerView = FooterView(context, /*attrs=*/ null)
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val ambientState =
+ AmbientState(
context,
dumpManager,
/* sectionProvider */ { _, _ -> false },
@@ -62,13 +74,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val featureFlags = mock<FeatureFlagsClassic>()
private val maxPanelHeight =
mContext.resources.displayMetrics.heightPixels -
- px(R.dimen.notification_panel_margin_top) -
- px(R.dimen.notification_panel_margin_bottom)
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
- testableResources.resources.getDimensionPixelSize(id).toFloat()
+ testableResources.resources.getDimensionPixelSize(id).toFloat()
private val bigGap = px(R.dimen.notification_section_divider_height)
private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
@@ -76,9 +89,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
@Before
fun setUp() {
Assume.assumeFalse(isTv())
-
+ mDependency.injectTestDependency(FeatureFlags::class.java, featureFlags)
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ whenever(notificationRow.entry).thenReturn(notificationEntry)
+ whenever(notificationRow.roundableState)
+ .thenReturn(RoundableState(notificationRow, notificationRow, 0f))
ambientState.isSmallScreen = true
hostView.addView(notificationRow)
@@ -92,7 +108,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun resetViewStates_defaultHun_yTranslationIsInset() {
whenever(notificationRow.isPinned).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
@@ -103,18 +119,87 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
- resetViewStates_hunYTranslationIsInset()
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
resetViewStates_stackMargin_changesHunYTranslation()
}
@Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_hunYTranslationIs(stackScrollAlgorithm.mHeadsUpInset)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
+ whenever(notificationRow.isPinned).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ resetViewStates_stackMargin_changesHunYTranslation()
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(maxHunTranslation)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
+ // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+ val bottomOfScreen = 2600f
+ val maxHunTranslation = 2000f
+ ambientState.maxHeadsUpTranslation = maxHunTranslation
+ ambientState.setLayoutMinHeight(2500) // Mock the height of shade
+ ambientState.stackY = 2500f // Scroll over the max translation
+ stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
+ stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt())
+ whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+ whenever(notificationRow.isHeadsUp).thenReturn(true)
+ whenever(notificationRow.isAboveShelf).thenReturn(true)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = bottomOfScreen + stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_newHeadsUpAnim_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -136,6 +221,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
fun resetViewStates_hunsOverlappingAndBottomHunAnimatingAway_bottomHunClipped() {
val topHun = mockExpandableNotificationRow()
val bottomHun = mockExpandableNotificationRow()
@@ -156,7 +242,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
@@ -174,33 +260,37 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
assertThat(notificationRow.viewState.alpha).isEqualTo(1f)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.25f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.25f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChangingWhileBouncerInTransit_viewBecomesTransparent() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.85f,
- expectedAlpha = 0.0f
+ expansionFraction = 0.85f,
+ expectedAlpha = 0.0f
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_expansionChanging_notificationAlphaUpdated() {
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.6f,
- expectedAlpha = getContentAlpha(0.6f)
+ expansionFraction = 0.6f,
+ expectedAlpha = getContentAlpha(0.6f)
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
@@ -216,13 +306,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
)
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -235,10 +326,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- verify(notificationShelf).updateState(
- /* algorithmState= */any(),
- /* ambientState= */eq(ambientState)
- )
+ verify(notificationShelf)
+ .updateState(/* algorithmState= */ any(), /* ambientState= */ eq(ambientState))
}
@Test
@@ -397,22 +486,31 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
@Test
fun getGapForLocation_onLockscreen_returnsSmallGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap)
}
@Test
fun getGapForLocation_goingToShade_interpolatesGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0.5f, /* onKeyguard= */ true)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0.5f,
+ /* onKeyguard= */ true
+ )
assertThat(gap).isEqualTo(smallGap * 0.5f + bigGap * 0.5f)
}
@Test
fun getGapForLocation_notOnLockscreen_returnsBigGap() {
- val gap = stackScrollAlgorithm.getGapForLocation(
- /* fractionToShade= */ 0f, /* onKeyguard= */ false)
+ val gap =
+ stackScrollAlgorithm.getGapForLocation(
+ /* fractionToShade= */ 0f,
+ /* onKeyguard= */ false
+ )
assertThat(gap).isEqualTo(bigGap)
}
@@ -469,12 +567,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = false
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 0f,
- /* maxHunY= */ 10f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 0f,
+ /* maxHunY= */ 10f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -484,12 +584,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 0f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 0f
+ )
assertFalse(expandableViewState.headsUpIsVisible)
}
@@ -499,12 +601,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ false,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ false,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -514,12 +618,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ false,
- /* isViewEndVisible= */ true,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ false,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -529,12 +635,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.headsUpIsVisible = true
- stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
- /* isShadeExpanded= */ true,
- /* mustStayOnScreen= */ true,
- /* isViewEndVisible= */ false,
- /* viewEnd= */ 10f,
- /* maxHunY= */ 1f)
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(
+ expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ false,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f
+ )
assertTrue(expandableViewState.headsUpIsVisible)
}
@@ -544,9 +652,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = 50f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) < viewY (50)
assertEquals(50f, expandableViewState.yTranslation)
@@ -557,9 +668,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val expandableViewState = ExpandableViewState()
expandableViewState.yTranslation = -10f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 1f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f,
+ expandableViewState
+ )
// qqs (10 + 0) > viewY (-10)
assertEquals(10f, expandableViewState.yTranslation)
@@ -571,9 +685,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
expandableViewState.height = 20
expandableViewState.yTranslation = -100f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, -100) = 10
// distToRealY = 10 - (-100f) = 110
@@ -587,9 +704,12 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
expandableViewState.height = 20
expandableViewState.yTranslation = 5f
- stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
- /* stackTranslation= */ 0f,
- /* collapsedHeight= */ 10f, expandableViewState)
+ stackScrollAlgorithm.clampHunToTop(
+ /* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f,
+ expandableViewState
+ )
// newTranslation = max(10, 5) = 10
// distToRealY = 10 - 5 = 5
@@ -599,41 +719,49 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
@Test
fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 110f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(1f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 90f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0.5f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 0f)
+ /* originalCornerRoundness= */ 0f
+ )
assertEquals(0f, currentRoundness)
}
@Test
fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
- val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ val currentRoundness =
+ stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
/* hostViewHeight= */ 100f,
/* stackY= */ 0f,
/* viewMaxHeight= */ 20f,
- /* originalCornerRoundness= */ 1f)
+ /* originalCornerRoundness= */ 1f
+ )
assertEquals(1f, currentRoundness)
}
@@ -642,23 +770,20 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Given: shade is opened, yTranslation of HUN is 0,
// the height of HUN equals to the height of QQS Panel,
// and HUN fully overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: full shadow would be applied
@@ -670,13 +795,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Given: shade is opened, yTranslation of HUN is greater than 0,
// the height of HUN is equal to the height of QQS Panel,
// and HUN partially overlaps with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = false,
- headerVisibleAmount = 1f
- )
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = false, headerVisibleAmount = 1f)
// Use half of the HUN's height as overlap
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
@@ -684,17 +806,17 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -702,28 +824,25 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
// the height of HUN is equal to the height of QQS Panel,
// and HUN doesn't overlap with QQS Panel
- ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
- px(R.dimen.qqs_layout_padding_bottom)
+ ambientState.stackTranslation =
+ px(R.dimen.qqs_layout_margin_top) + px(R.dimen.qqs_layout_padding_bottom)
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = true,
- fullyVisible = true,
- headerVisibleAmount = 1f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = true, fullyVisible = true, headerVisibleAmount = 1f)
// HUN doesn't overlap with QQS Panel
- childHunView.viewState.yTranslation = ambientState.topPadding +
- ambientState.stackTranslation
+ childHunView.viewState.yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should not have shadow
@@ -737,11 +856,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0f)
childHunView.viewState.yTranslation = 0f
// Shade is closed, thus childHunView's headerVisibleAmount is 0
childHunView.headerVisibleAmount = 0f
@@ -750,11 +866,11 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have full shadow
@@ -768,11 +884,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
ambientState.stackTranslation = -ambientState.topPadding
// Mock the height of shade
ambientState.setLayoutMinHeight(1000)
- val childHunView = createHunViewMock(
- isShadeOpen = false,
- fullyVisible = false,
- headerVisibleAmount = 0.5f
- )
+ val childHunView =
+ createHunViewMock(isShadeOpen = false, fullyVisible = false, headerVisibleAmount = 0.5f)
childHunView.viewState.yTranslation = 0f
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
// use 0.5 as headerVisibleAmount here
@@ -782,17 +895,17 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
- /* i= */ 0,
- /* childrenOnTop= */ 0.0f,
- /* StackScrollAlgorithmState= */ algorithmState,
- /* ambientState= */ ambientState,
- /* shouldElevateHun= */ true
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
)
// Then: HUN should have shadow, but not as full size
assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
assertThat(childHunView.viewState.zTranslation)
- .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
@Test
@@ -862,134 +975,174 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// stackScrollAlgorithm.resetViewStates is called.
ambientState.dozeAmount = 0.5f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 0.5f
+ ambientState,
+ algorithmState,
+ fraction = 0.5f
)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
// Then: pulsingNotificationView should show at full height
assertEquals(
- stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
- pulsingNotificationView.viewState.height
+ stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
+ pulsingNotificationView.viewState.height
)
// After: reset dozeAmount and expansionFraction
ambientState.dozeAmount = 0f
setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState,
- algorithmState,
- fraction = 1f
+ ambientState,
+ algorithmState,
+ fraction = 1f
)
}
// region shouldPinHunToBottomOfExpandedQs
@Test
fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */false,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ false,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */true,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /*headsUpOnKeyguard=*/false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ true,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /*headsUpOnKeyguard=*/ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */true,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ true,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */false
- )).isFalse()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isFalse()
}
@Test
fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/false,
- /* headsUpOnKeyguard= */false
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ false,
+ /* headsUpOnKeyguard= */ false
+ )
+ )
+ .isTrue()
}
@Test
fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() {
- assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
- /* mustStayOnScreen= */true,
- /* headsUpIsVisible= */false,
- /* showingPulsing= */false,
- /* isOnKeyguard=*/true,
- /* headsUpOnKeyguard= */true
- )).isTrue()
+ assertThat(
+ stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+ /* mustStayOnScreen= */ true,
+ /* headsUpIsVisible= */ false,
+ /* showingPulsing= */ false,
+ /* isOnKeyguard=*/ true,
+ /* headsUpOnKeyguard= */ true
+ )
+ )
+ .isTrue()
}
- // endregion
- private fun createHunViewMock(
- isShadeOpen: Boolean,
- fullyVisible: Boolean,
- headerVisibleAmount: Float
- ) =
- mock<ExpandableNotificationRow>().apply {
- val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
- whenever(this.viewState).thenReturn(childViewStateMock)
-
- whenever(this.mustStayOnScreen()).thenReturn(true)
- whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ @Test
+ fun shouldHunAppearFromBottom_hunAtMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
+ ExpandableViewState().apply {
+ height = 100
+ yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max
}
+ assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+ }
- private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ @Test
+ fun shouldHunAppearFromBottom_hunBelowMaxHunTranslation() {
+ ambientState.maxHeadsUpTranslation = 400f
+ val viewState =
ExpandableViewState().apply {
- // Mock the HUN's height with ambientState.topPadding +
- // ambientState.stackTranslation
- height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
- if (isShadeOpen && fullyVisible) {
- yTranslation =
- ambientState.topPadding + ambientState.stackTranslation
- } else {
- yTranslation = 0f
- }
- headsUpIsVisible = fullyVisible
+ height = 100
+ yTranslation =
+ ambientState.maxHeadsUpTranslation - height - 1 // move it below the max
}
- private fun createPulsingViewMock(
+ assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+ }
+ // endregion
+
+ private fun createHunViewMock(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
) =
- mock<ExpandableNotificationRow>().apply {
- whenever(this.viewState).thenReturn(ExpandableViewState())
- whenever(this.showingPulsing()).thenReturn(true)
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
+
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
+
+ private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation = ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
}
+ headsUpIsVisible = fullyVisible
+ }
+
+ private fun createPulsingViewMock() =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.viewState).thenReturn(ExpandableViewState())
+ whenever(this.showingPulsing()).thenReturn(true)
+ }
private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
- ambientState: AmbientState,
- algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
- fraction: Float
+ ambientState: AmbientState,
+ algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
+ fraction: Float
) {
// showingShelf: false
algorithmState.firstViewInShelf = null
@@ -1002,11 +1155,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
ambientState.stackHeight = ambientState.stackEndHeight * fraction
}
- private fun resetViewStates_hunYTranslationIsInset() {
+ private fun resetViewStates_hunYTranslationIs(expected: Float) {
stackScrollAlgorithm.resetViewStates(ambientState, 0)
- assertThat(notificationRow.viewState.yTranslation)
- .isEqualTo(stackScrollAlgorithm.mHeadsUpInset)
+ assertThat(notificationRow.viewState.yTranslation).isEqualTo(expected)
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
@@ -1025,13 +1177,13 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
private fun resetViewStates_hunsOverlapping_bottomHunClipped(
- topHun: ExpandableNotificationRow,
- bottomHun: ExpandableNotificationRow
+ topHun: ExpandableNotificationRow,
+ bottomHun: ExpandableNotificationRow
) {
- val topHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_content_min_height)
- val bottomHunHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.notification_max_heads_up_height)
+ val topHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_content_min_height)
+ val bottomHunHeight =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_max_heads_up_height)
whenever(topHun.intrinsicHeight).thenReturn(topHunHeight)
whenever(bottomHun.intrinsicHeight).thenReturn(bottomHunHeight)
@@ -1054,8 +1206,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction: Float,
- expectedAlpha: Float,
+ expansionFraction: Float,
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
new file mode 100644
index 000000000000..5a5703512a39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.description
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+
+private const val VIEW_HEIGHT = 100
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StackStateAnimatorTest : SysuiTestCase() {
+
+ private lateinit var stackStateAnimator: StackStateAnimator
+ private val stackScroller: NotificationStackScrollLayout = mock()
+ private val view: ExpandableView = mock()
+ private val viewState: ExpandableViewState =
+ ExpandableViewState().apply { height = VIEW_HEIGHT }
+ private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
+ @Before
+ fun setUp() {
+ whenever(stackScroller.context).thenReturn(context)
+ whenever(view.viewState).thenReturn(viewState)
+ stackStateAnimator = StackStateAnimator(stackScroller)
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
+ val topMargin = 50f
+ val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ stackStateAnimator.setStackTopMargin(topMargin.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
+ val screenHeight = 2000f
+ val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ val event =
+ AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR).apply {
+ headsUpFromBottom = true
+ }
+ stackStateAnimator.setHeadsUpAppearHeightBottom(screenHeight.toInt())
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setActualHeight(VIEW_HEIGHT, false)
+ verify(view, description("should animate from the bottom")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* onEndRunnable= */ null
+ )
+ }
+
+ @Test
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view)
+ .performRemoveAnimation(
+ /* duration= */ eq(ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()),
+ /* delay= */ eq(0L),
+ /* translationDirection= */ eq(0f),
+ /* isHeadsUpAnimation= */ eq(true),
+ /* onStartedRunnable= */ any(),
+ /* onFinishedRunnable= */ runnableCaptor.capture(),
+ /* animationListener= */ any()
+ )
+
+ runnableCaptor.value.run() // execute the end runnable
+
+ verify(view, description("should be called at the end of the animation"))
+ .removeFromTransientContainer()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
index da543d4454b8..cd6bb5f4966a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
-import android.util.Log
-import android.util.Log.TerribleFailureHandler
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertDoesNotLogWtf
+import com.android.systemui.log.assertLogsWtf
import kotlin.math.log2
import kotlin.math.sqrt
import org.junit.Assert
@@ -32,61 +32,36 @@ import org.junit.runner.RunWith
class ViewStateTest : SysuiTestCase() {
private val viewState = ViewState()
- private var wtfHandler: TerribleFailureHandler? = null
- private var wtfCount = 0
-
@Suppress("DIVISION_BY_ZERO")
@Test
fun testWtfs() {
- interceptWtfs()
-
// Setting valid values doesn't cause any wtfs.
- viewState.alpha = 0.1f
- viewState.xTranslation = 0f
- viewState.yTranslation = 10f
- viewState.zTranslation = 20f
- viewState.scaleX = 0.5f
- viewState.scaleY = 0.25f
-
- expectWtfs(0)
+ assertDoesNotLogWtf {
+ viewState.alpha = 0.1f
+ viewState.xTranslation = 0f
+ viewState.yTranslation = 10f
+ viewState.zTranslation = 20f
+ viewState.scaleX = 0.5f
+ viewState.scaleY = 0.25f
+ }
// Setting NaN values leads to wtfs being logged, and the value not being changed.
- viewState.alpha = 0.0f / 0.0f
- expectWtfs(1)
+ assertLogsWtf { viewState.alpha = 0.0f / 0.0f }
Assert.assertEquals(viewState.alpha, 0.1f)
- viewState.xTranslation = Float.NaN
- expectWtfs(2)
+ assertLogsWtf { viewState.xTranslation = Float.NaN }
Assert.assertEquals(viewState.xTranslation, 0f)
- viewState.yTranslation = log2(-10.0).toFloat()
- expectWtfs(3)
+ assertLogsWtf { viewState.yTranslation = log2(-10.0).toFloat() }
Assert.assertEquals(viewState.yTranslation, 10f)
- viewState.zTranslation = sqrt(-1.0).toFloat()
- expectWtfs(4)
+ assertLogsWtf { viewState.zTranslation = sqrt(-1.0).toFloat() }
Assert.assertEquals(viewState.zTranslation, 20f)
- viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY
- expectWtfs(5)
+ assertLogsWtf { viewState.scaleX = Float.POSITIVE_INFINITY + Float.NEGATIVE_INFINITY }
Assert.assertEquals(viewState.scaleX, 0.5f)
- viewState.scaleY = Float.POSITIVE_INFINITY * 0
- expectWtfs(6)
+ assertLogsWtf { viewState.scaleY = Float.POSITIVE_INFINITY * 0 }
Assert.assertEquals(viewState.scaleY, 0.25f)
}
-
- private fun interceptWtfs() {
- wtfCount = 0
- wtfHandler =
- Log.setWtfHandler { _: String?, e: Log.TerribleFailure, _: Boolean ->
- Log.e("ViewStateTest", "Observed WTF: $e")
- wtfCount++
- }
- }
-
- private fun expectWtfs(expectedWtfCount: Int) {
- Assert.assertNotNull(wtfHandler)
- Assert.assertEquals(expectedWtfCount, wtfCount)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 62d8f7fa894d..9f4e1dd8cc4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -73,6 +74,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
@Test
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10)
@@ -151,7 +153,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
iconContainer.addView(iconFive)
assertEquals(5, iconContainer.childCount)
- val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
+ val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
@@ -212,6 +214,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
@Test
fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
val forceOverflow =
iconContainer.shouldForceOverflow(
/* i= */ 1,
@@ -228,7 +231,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
iconContainer.shouldForceOverflow(
/* i= */ 10,
/* speedBumpIndex= */ 11,
- /* iconAppearAmount= */ 0f,
+ /* iconAppearAmount= */ 0.1f,
/* maxVisibleIcons= */ 5
)
assertTrue(forceOverflow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 7594c90daa8b..feff046bb708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.phone
import android.graphics.Point
+import android.testing.TestableLooper
import android.view.Display
import android.view.Surface
import android.view.View
@@ -19,6 +20,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
+@TestableLooper.RunWithLooper
class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 14751c2dc29e..54d3607c7a83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -36,6 +36,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -91,7 +92,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private NotificationIconAreaController mMockNotificationAreaController;
private ShadeExpansionStateManager mShadeExpansionStateManager;
- private View mNotificationAreaInner;
private OngoingCallController mOngoingCallController;
private SystemStatusAnimationScheduler mAnimationScheduler;
private StatusBarLocationPublisher mLocationPublisher;
@@ -270,15 +270,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -310,7 +310,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are hidden
assertEquals(View.INVISIBLE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@@ -326,7 +326,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -343,7 +343,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are hidden
assertEquals(View.INVISIBLE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
// WHEN the shade is updated to no longer be open
@@ -354,7 +354,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -368,7 +368,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -382,7 +382,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are hidden
assertEquals(View.GONE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@@ -396,7 +396,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are hidden
assertEquals(View.GONE, getClockView().getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
// WHEN the transition has finished
@@ -405,7 +405,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
// THEN all views are shown
assertEquals(View.VISIBLE, getClockView().getVisibility());
- assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@@ -438,7 +438,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
assertEquals(View.VISIBLE,
mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -503,8 +503,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, 0, 0, true);
// Notification area is hidden without delay
- assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01);
- assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+ assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
+ assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@Test
@@ -723,11 +723,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private void setUpNotificationIconAreaController() {
mMockNotificationAreaController = mock(NotificationIconAreaController.class);
-
- mNotificationAreaInner = new View(mContext);
-
- when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn(
- mNotificationAreaInner);
+ View notificationAreaInner =
+ LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
+ when(mMockNotificationAreaController.getNotificationInnerAreaView())
+ .thenReturn(notificationAreaInner);
}
/**
@@ -782,4 +781,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private View getEndSideContentView() {
return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
}
+
+ private View getNotificationAreaView() {
+ return mFragment.getView().findViewById(R.id.notificationIcons);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index e461e3f7fb1a..bbc96f703738 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -26,8 +26,11 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
private var currentRecording: UnfoldTransitionRecording? = null
+ var lastCallbackThread: Thread? = null
+ private set
override fun onTransitionStarted() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Trying to start a transition when it is already in progress")
.that(currentRecording)
.isNull()
@@ -36,6 +39,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionProgress(progress: Float) {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition progress event when it's not started")
.that(currentRecording)
.isNotNull()
@@ -43,6 +47,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionFinishing() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition finishing event when it's not started")
.that(currentRecording)
.isNotNull()
@@ -50,6 +55,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionFinished() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording)
.isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index a25469bfc09b..d864d53fea32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Mock lateinit var rotationChangeProvider: RotationChangeProvider
@@ -48,10 +50,12 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+ private lateinit var testableLooper : TestableLooper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
progressProvider =
NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
@@ -123,5 +127,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
private fun onRotationChanged(rotation: Int) {
rotationListenerCaptor.value.onRotationChanged(rotation)
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index e1e54a903917..2f29b3bdd3b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -19,6 +19,7 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.provider.Settings
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -36,6 +37,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
@Mock lateinit var sinkProvider: TransitionProgressListener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
new file mode 100644
index 000000000000..5b4f4d37214f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold.util
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Process
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.progress.TestUnfoldProgressListener
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withTimeout
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
+
+ private val rootProvider = TestUnfoldTransitionProvider()
+ private val listener = TestUnfoldProgressListener()
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val bgThread =
+ HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() }
+ private val bgHandler = Handler(bgThread.looper)
+ private val scopedProvider =
+ ScopedUnfoldTransitionProgressProvider(rootProvider).apply { addCallback(listener) }
+
+ @Test
+ fun setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks() =
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ scopedProvider.setReadyToHandleTransition(true)
+
+ runBlockingInBg { /* sync barrier */}
+
+ listener.assertStarted()
+
+ runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+ listener.assertLastProgress(1f)
+
+ runBlockingInBg { rootProvider.onTransitionFinished() }
+
+ listener.assertNotStarted()
+ }
+
+ @Test
+ fun setReadyToHandleTransition_whileTransitionNotRunning_callbacksInProgressThread() {
+ testScope.runTest {
+ scopedProvider.setReadyToHandleTransition(true)
+
+ val bgThread = runBlockingInBg { Thread.currentThread() }
+
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ listener.assertStarted()
+
+ assertThat(listener.lastCallbackThread).isEqualTo(bgThread)
+ }
+ }
+
+ @Test
+ fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
+ testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
+ }
+
+ @Test
+ fun onTransitionStarted_whileNotReadyToHandleTransition_doesNotPropagate() {
+ testScope.runTest {
+ scopedProvider.setReadyToHandleTransition(false)
+
+ rootProvider.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+ }
+
+ @Test
+ fun onTransitionStarted_defaultReadiness_doesNotPropagate() {
+ testScope.runTest {
+ rootProvider.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+ }
+
+ @Test
+ fun onTransitionStarted_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg {
+ rootProvider.onTransitionStarted()
+ rootProvider.onTransitionFinished()
+ }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionStarted() }
+ }
+ }
+
+ @Test
+ fun onTransitionProgress_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) {
+ rootProvider.onTransitionProgress(1f)
+ }
+ }
+ }
+
+ @Test
+ fun onTransitionFinished_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinished() }
+ }
+ }
+
+ @Test
+ fun onTransitionFinishing_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinishing() }
+ }
+ }
+
+ private fun <T> runBlockingInBg(f: () -> T): T {
+ return runBlocking {
+ withTimeout(5.seconds) {
+ suspendCancellableCoroutine { c: CancellableContinuation<T> ->
+ bgHandler.post { c.resumeWith(Result.success(f())) }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index 4a38fc069d9f..f484ea04bb4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -27,6 +28,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
@@ -54,9 +56,7 @@ class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
- with(listener.ensureTransitionFinished()) {
- assertLastProgress(0.5f)
- }
+ with(listener.ensureTransitionFinished()) { assertLastProgress(0.5f) }
}
@Test
@@ -121,8 +121,6 @@ class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
sourceProvider.onTransitionProgress(0.1f)
sourceProvider.onTransitionFinished()
- with(listener.ensureTransitionFinished()) {
- assertLastProgress(0.1f)
- }
+ with(listener.ensureTransitionFinished()) { assertLastProgress(0.1f) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
index e59e4759fc7b..78016840c3fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -28,8 +28,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.model.SysUiStateTest
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleEducationController
-import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION
-import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION
+import com.android.wm.shell.bubbles.ManageEducationView.Companion.PREF_MANAGED_EDUCATION
+import com.android.wm.shell.bubbles.StackEducationView.Companion.PREF_STACK_EDUCATION
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Assert.assertEquals
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 b217195000b4..814ea19a7dcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -29,8 +29,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -50,6 +48,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -186,7 +186,7 @@ import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.bubbles.StackEducationViewKt;
+import com.android.wm.shell.bubbles.StackEducationView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -1930,7 +1930,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testShowStackEdu_isNotConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1948,7 +1948,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testShowStackEdu_isConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, false);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1966,7 +1966,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testShowStackEdu_isSeenConversationBubble() {
// Setup
- setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true);
+ setPrefBoolean(StackEducationView.PREF_STACK_EDUCATION, true);
BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */);
mBubbleController.updateBubble(bubbleEntry);
assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
index a464fa80d629..fa7958041641 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt
@@ -16,10 +16,8 @@
package com.android.systemui.accessibility.data.repository
-import android.view.accessibility.accessibilityManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.accessibilityRepository by Fixture {
- AccessibilityRepository.invoke(a11yManager = accessibilityManager)
-}
+val Kosmos.fakeAccessibilityRepository by Fixture { FakeAccessibilityRepository() }
+val Kosmos.accessibilityRepository by Fixture { fakeAccessibilityRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 7c5696c716d8..848650810582 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,7 +20,6 @@ import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationResultModel
@@ -29,7 +28,6 @@ import dagger.Binds
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -40,15 +38,11 @@ class FakeAuthenticationRepository(
private val currentTime: () -> Long,
) : AuthenticationRepository {
- override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
override val hintedPinLength: Int = HINTING_PIN_LENGTH
private val _isPatternVisible = MutableStateFlow(true)
override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
- override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
override val hasLockoutOccurred = MutableStateFlow(false)
private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
@@ -68,8 +62,6 @@ class FakeAuthenticationRepository(
override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
_isPinEnhancedPrivacyEnabled.asStateFlow()
- private var failedAttemptCount = 0
- private var lockoutEndTimestamp = 0L
private var credentialOverride: List<Any>? = null
private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
@@ -89,11 +81,27 @@ class FakeAuthenticationRepository(
}
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
- failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
- authenticationChallengeResult.emit(isSuccessful)
+ if (isSuccessful) {
+ _failedAuthenticationAttempts.value = 0
+ _lockoutEndTimestamp = null
+ hasLockoutOccurred.value = false
+ lockoutStartedReportCount = 0
+ } else {
+ _failedAuthenticationAttempts.value++
+ }
}
+ private var _failedAuthenticationAttempts = MutableStateFlow(0)
+ override val failedAuthenticationAttempts: StateFlow<Int> =
+ _failedAuthenticationAttempts.asStateFlow()
+
+ private var _lockoutEndTimestamp: Long? = null
+ override val lockoutEndTimestamp: Long?
+ get() = if (currentTime() < (_lockoutEndTimestamp ?: 0)) _lockoutEndTimestamp else null
+
override suspend fun reportLockoutStarted(durationMs: Int) {
+ _lockoutEndTimestamp = (currentTime() + durationMs).takeIf { durationMs > 0 }
+ hasLockoutOccurred.value = true
lockoutStartedReportCount++
}
@@ -101,25 +109,10 @@ class FakeAuthenticationRepository(
return (credentialOverride ?: DEFAULT_PIN).size
}
- override suspend fun getFailedAuthenticationAttemptCount(): Int {
- return failedAttemptCount
- }
-
- override suspend fun getLockoutEndTimestamp(): Long {
- return lockoutEndTimestamp
- }
-
fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
_isAutoConfirmFeatureEnabled.value = isEnabled
}
- override suspend fun setLockoutDuration(durationMs: Int) {
- lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
- if (durationMs > 0) {
- hasLockoutOccurred.value = true
- }
- }
-
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
@@ -136,8 +129,8 @@ class FakeAuthenticationRepository(
else -> error("Unexpected credential type ${credential.type}!")
}
- return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- hasLockoutOccurred.value = false
+ val failedAttempts = _failedAuthenticationAttempts.value
+ return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
AuthenticationResultModel(
isSuccessful = isSuccessful,
lockoutDurationMs = 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 060ca4c7e912..05cb059a00cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -19,17 +19,11 @@ package com.android.systemui.authentication.domain.interactor
import com.android.systemui.authentication.data.repository.authenticationRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.user.data.repository.userRepository
-import com.android.systemui.util.time.fakeSystemClock
val Kosmos.authenticationInteractor by
Kosmos.Fixture {
AuthenticationInteractor(
applicationScope = applicationCoroutineScope,
repository = authenticationRepository,
- backgroundDispatcher = testDispatcher,
- userRepository = userRepository,
- clock = fakeSystemClock,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
index dca151db8b58..7f707850e3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.biometrics
-import android.graphics.Rect
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.mock
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardViewModel @Inject constructor() {
- var sensorBounds: Rect = Rect()
-}
+var Kosmos.authController by Fixture { mock<AuthController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
new file mode 100644
index 000000000000..cbfc7686a896
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.authController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.udfpsOverlayInteractor by Fixture {
+ UdfpsOverlayInteractor(
+ context = applicationContext,
+ authController = authController,
+ selectedUserInteractor = selectedUserInteractor,
+ scope = applicationCoroutineScope,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt
new file mode 100644
index 000000000000..9175f093c171
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.ui.viewmodel
+
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.udfpsAccessibilityOverlayViewModel by
+ Kosmos.Fixture {
+ UdfpsAccessibilityOverlayViewModel(
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
new file mode 100644
index 000000000000..4bfe4f571b05
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntryForegroundIconViewModel by Fixture {
+ DeviceEntryForegroundViewModel(
+ context = applicationContext,
+ configurationRepository = configurationRepository,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ deviceEntryIconViewModel = deviceEntryIconViewModel,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 67e9289f5d92..5ceefde32d2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -28,8 +28,10 @@ import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
+
val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture {
- mutableSetOf<DeviceEntryIconTransition>()
+ setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
}
val Kosmos.deviceEntryIconViewModel by Fixture {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
index d894a1139eeb..6d872a3d8028 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt
@@ -16,11 +16,16 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
-@ExperimentalCoroutinesApi
-class UdfpsKeyguardInternalViewModel
-@Inject
-constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
+class FakeDeviceEntryIconTransition : DeviceEntryIconTransition {
+ private val _deviceEntryParentViewAlpha: MutableStateFlow<Float> = MutableStateFlow(0f)
+ override val deviceEntryParentViewAlpha: Flow<Float> = _deviceEntryParentViewAlpha.asStateFlow()
+
+ fun setDeviceEntryParentViewAlpha(alpha: Float) {
+ _deviceEntryParentViewAlpha.value = alpha
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 6ccb3bc2812e..5e67182d7353 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -20,6 +20,29 @@ import android.util.Log
import android.util.Log.TerribleFailureHandler
import junit.framework.Assert
+/** Asserts that the given block does not make a call to Log.wtf */
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingBlock: () -> Unit,
+) {
+ var caught: TerribleFailureLog? = null
+ val newHandler = TerribleFailureHandler { tag, failure, system ->
+ caught = TerribleFailureLog(tag, failure, system)
+ }
+ val oldHandler = Log.setWtfHandler(newHandler)
+ try {
+ notLoggingBlock()
+ } finally {
+ Log.setWtfHandler(oldHandler)
+ }
+ caught?.let { throw AssertionError("$message: $it", it.failure) }
+}
+
+fun assertDoesNotLogWtf(
+ message: String = "Expected Log.wtf not to be called",
+ notLoggingRunnable: Runnable,
+) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() }
+
/**
* Assert that the given block makes a call to Log.wtf
*
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0b41926ed13e..25b97b3dab5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -91,7 +91,6 @@ import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.data.repository.TelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -174,7 +173,7 @@ class SceneTestUtils(
mobileConnectionsRepository = mobileConnectionsRepository,
)
- val userRepository: UserRepository by lazy {
+ val userRepository: FakeUserRepository by lazy {
FakeUserRepository().apply {
val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0))
setUserInfos(users)
@@ -236,9 +235,6 @@ class SceneTestUtils(
return AuthenticationInteractor(
applicationScope = applicationScope(),
repository = repository,
- backgroundDispatcher = testDispatcher,
- userRepository = userRepository,
- clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
@@ -274,7 +270,6 @@ class SceneTestUtils(
repository = bouncerRepository,
authenticationInteractor = authenticationInteractor,
keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
- flags = sceneContainerFlags,
falsingInteractor = falsingInteractor(),
powerInteractor = powerInteractor(),
simBouncerInteractor = simBouncerInteractor,
@@ -312,6 +307,7 @@ class SceneTestUtils(
userSwitcherMenu = flowOf(createMenuActions()),
actionButtonInteractor = actionButtonInteractor,
simBouncerInteractor = simBouncerInteractor,
+ clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
index 5c8fe0d5a81e..2d2f546a05b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.wm.shell.bubbles.bubblesOptional
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,6 +44,7 @@ val Kosmos.notificationIconsInteractor by Fixture {
NotificationIconsInteractor(
activeNotificationsInteractor = activeNotificationsInteractor,
bubbles = bubblesOptional,
+ headsUpNotificationIconInteractor = headsUpNotificationIconInteractor,
keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
)
}
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce12f05..e52cefb2d7e4 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,4 +39,7 @@ android_library {
sdk_version: "current",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index f9751d9c279c..2bca2722be6c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -15,8 +15,11 @@
*/
package com.android.systemui.unfold.util
+import android.os.Handler
+import android.os.Looper
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.util.concurrent.CopyOnWriteArrayList
/**
* Manages progress listeners that can have smaller lifespan than the unfold animation.
@@ -33,12 +36,13 @@ open class ScopedUnfoldTransitionProgressProvider
constructor(source: UnfoldTransitionProgressProvider? = null) :
UnfoldTransitionProgressProvider, TransitionProgressListener {
+ private var progressHandler: Handler? = null
private var source: UnfoldTransitionProgressProvider? = null
- private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+ private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
- private var isReadyToHandleTransition = false
- private var isTransitionRunning = false
+ @Volatile private var isReadyToHandleTransition = false
+ @Volatile private var isTransitionRunning = false
private var lastTransitionProgress = PROGRESS_UNSET
init {
@@ -70,15 +74,18 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
* Call it with readyToHandleTransition = false when listeners can't process the events.
*/
fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
- if (isTransitionRunning) {
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
- if (lastTransitionProgress != PROGRESS_UNSET) {
- listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ val progressHandler = this.progressHandler
+ if (isTransitionRunning && progressHandler != null) {
+ progressHandler.post {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ if (lastTransitionProgress != PROGRESS_UNSET) {
+ listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ }
+ } else {
+ isTransitionRunning = false
+ listeners.forEach { it.onTransitionFinished() }
}
- } else {
- isTransitionRunning = false
- listeners.forEach { it.onTransitionFinished() }
}
}
this.isReadyToHandleTransition = isReadyToHandleTransition
@@ -98,6 +105,7 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionStarted() {
+ assertInProgressThread()
isTransitionRunning = true
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionStarted() }
@@ -105,6 +113,7 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionProgress(progress: Float) {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionProgress(progress) }
}
@@ -112,12 +121,14 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionFinishing() {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinishing() }
}
}
override fun onTransitionFinished() {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinished() }
}
@@ -125,6 +136,21 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
lastTransitionProgress = PROGRESS_UNSET
}
+ private fun assertInProgressThread() {
+ val cachedProgressHandler = progressHandler
+ if (cachedProgressHandler == null) {
+ val thisLooper = Looper.myLooper() ?: error("This thread is expected to have a looper.")
+ progressHandler = Handler(thisLooper)
+ } else {
+ check(cachedProgressHandler.looper.isCurrentThread) {
+ """Receiving unfold transition callback from different threads.
+ |Current: ${Thread.currentThread()}
+ |expected: ${cachedProgressHandler.looper.thread}"""
+ .trimMargin()
+ }
+ }
+ }
+
companion object {
private const val PROGRESS_UNSET = -1f
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fcaf032a..491ed22ce2c8 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@ android.os.PackageTagsList
android.os.Parcel
android.os.Parcelable
android.os.Process
+android.os.ServiceSpecificException
android.os.SystemClock
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index a4b28967e3b2..cab2d74c27d1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.appwidget;
+import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -144,6 +145,7 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -277,7 +279,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mKeyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mSaveStateHandler = BackgroundThread.getHandler();
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler = new Handler(BackgroundThread.get().getLooper(),
+ this::handleSaveMessage);
+ } else {
+ mSaveStateHandler = BackgroundThread.getHandler();
+ }
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -314,6 +321,40 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mMaxWidgetBitmapMemory = 6 * size.x * size.y;
}
+ private boolean handleSaveMessage(Message msg) {
+ final int userId = msg.what;
+ SparseArray<byte[]> userIdToBytesMapping;
+ synchronized (mLock) {
+ // No need to enforce unlocked state when there is no caller. User can be in the
+ // stopping state or removed by the time the message is processed
+ ensureGroupStateLoadedLocked(userId, false /* enforceUserUnlockingOrUnlocked */);
+ userIdToBytesMapping = saveStateToByteArrayLocked(userId);
+ }
+
+ for (int i = 0; i < userIdToBytesMapping.size(); i++) {
+ int currentProfileId = userIdToBytesMapping.keyAt(i);
+ byte[] currentStateByteArray = userIdToBytesMapping.valueAt(i);
+ AtomicFile currentFile = getSavedStateFile(currentProfileId);
+ FileOutputStream fileStream;
+ try {
+ fileStream = currentFile.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to start writing stream", e);
+ continue;
+ }
+
+ try {
+ fileStream.write(currentStateByteArray);
+ currentFile.finishWrite(fileStream);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write state byte stream to file", e);
+ currentFile.failWrite(fileStream);
+ }
+ }
+
+ return true;
+ }
+
private void registerBroadcastReceiver() {
// Register for broadcasts about package install, etc., so we can
// update the provider list.
@@ -1944,7 +1985,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
private void saveGroupStateAsync(int groupId) {
- mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ if (removeAppWidgetServiceIoFromCriticalPath()) {
+ mSaveStateHandler.removeMessages(groupId);
+ mSaveStateHandler.sendEmptyMessage(groupId);
+ } else {
+ mSaveStateHandler.post(new SaveStateRunnable(groupId));
+ }
}
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
@@ -3104,6 +3150,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
@GuardedBy("mLock")
+ private @NonNull SparseArray<byte[]> saveStateToByteArrayLocked(int userId) {
+ tagProvidersAndHosts();
+
+ final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
+ SparseArray<byte[]> userIdToBytesMapping = new SparseArray<>();
+
+ for (int profileId : profileIds) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ if (writeProfileStateToStreamLocked(outputStream, profileId)) {
+ userIdToBytesMapping.put(profileId, outputStream.toByteArray());
+ }
+ }
+
+ return userIdToBytesMapping;
+ }
+
+ @GuardedBy("mLock")
private void saveStateLocked(int userId) {
tagProvidersAndHosts();
@@ -3117,7 +3180,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
FileOutputStream stream;
try {
stream = file.startWrite();
- if (writeProfileStateToFileLocked(stream, profileId)) {
+ if (writeProfileStateToStreamLocked(stream, profileId)) {
file.finishWrite(stream);
} else {
file.failWrite(stream);
@@ -3158,7 +3221,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
@GuardedBy("mLock")
- private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
+ private boolean writeProfileStateToStreamLocked(OutputStream stream, int userId) {
int N;
try {
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index d9741c8e867d..4a6d5c9bc65e 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,9 +25,6 @@ import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
-import java.util.Objects;
-
-
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
* either a Credential Autofill Service or the user-opted autofill service.
@@ -51,7 +48,6 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal
private final RemoteFillService mRemoteFillService;
private final SecondaryProviderCallback mCallback;
- private FillRequest mLastFillRequest;
private int mLastFlag;
SecondaryProviderHandler(
@@ -97,17 +93,11 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal
}
/**
- * Requests a new fill response. If the fill request is same as the last requested fill request,
- * then the request is duped.
+ * Requests a new fill response.
*/
public void onFillRequest(FillRequest pendingFillRequest, int flag) {
- if (Objects.equals(pendingFillRequest, mLastFillRequest)) {
- Slog.v(TAG, "Deduping fill request to secondary provider.");
- return;
- }
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
- mLastFillRequest = pendingFillRequest;
mRemoteFillService.onFillRequest(pendingFillRequest);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d527ce0e1b2a..c4e8f12b72f5 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -367,6 +367,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private SparseArray<FillResponse> mResponses;
+ @GuardedBy("mLock")
+ private SparseArray<FillResponse> mSecondaryResponses;
+
/**
* Contexts read from the app; they will be updated (sanitized, change values for save) before
* sent to {@link AutofillService}. Ordered by the time they were read.
@@ -713,7 +716,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mPendingFillRequest.getDelayedFillIntentSender());
}
mLastFillRequest = mPendingFillRequest;
- mRemoteFillService.onFillRequest(mPendingFillRequest);
+ if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
+ && mSecondaryProviderHandler != null) {
+ Slog.v(TAG, "Requesting fill response to secondary provider.");
+ mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
+ mPendingFillRequest.getFlags());
+ } else if (mRemoteFillService != null) {
+ mRemoteFillService.onFillRequest(mPendingFillRequest);
+ }
mPendingInlineSuggestionsRequest = null;
mWaitForInlineRequest = false;
mPendingFillRequest = null;
@@ -1196,7 +1206,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
- final FillResponse existingResponse = viewState.getResponse();
+ final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+ ? viewState.getSecondaryResponse() : viewState.getResponse();
mFillRequestEventLogger.startLogForNewRequest();
mRequestCount++;
mFillRequestEventLogger.maybeSetAppPackageUid(uid);
@@ -1804,6 +1815,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
synchronized (mLock) {
+ if (mSecondaryResponses == null) {
+ mSecondaryResponses = new SparseArray<>(2);
+ }
+ mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse);
setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
/* isPrimary= */ false);
@@ -3980,7 +3995,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// If it's not, then check if it should start a partition.
- if (shouldStartNewPartitionLocked(id)) {
+ if (shouldStartNewPartitionLocked(id, flags)) {
if (sDebug) {
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
@@ -4008,9 +4023,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* @return {@code true} if a new partition should be started
*/
@GuardedBy("mLock")
- private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
+ private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) {
final ViewState currentView = mViewStates.get(id);
- if (mResponses == null) {
+ SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags)
+ ? mSecondaryResponses : mResponses;
+ if (responses == null) {
return currentView != null && (currentView.getState()
& ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
}
@@ -4022,7 +4039,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return true;
}
- final int numResponses = mResponses.size();
+ final int numResponses = responses.size();
if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
+ " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
@@ -4030,7 +4047,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
for (int responseNum = 0; responseNum < numResponses; responseNum++) {
- final FillResponse response = mResponses.valueAt(responseNum);
+ final FillResponse response = responses.valueAt(responseNum);
if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
return false;
@@ -4066,6 +4083,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
boolean shouldRequestSecondaryProvider(int flags) {
+ if (!mService.isAutofillCredmanIntegrationEnabled()
+ || mSecondaryProviderHandler == null) {
+ return false;
+ }
if (mIsPrimaryCredential) {
return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0;
} else {
@@ -4205,12 +4226,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
break;
case ACTION_VIEW_ENTERED:
- if (shouldRequestSecondaryProvider(flags)
- && mSecondaryProviderHandler != null
- && mAssistReceiver.mLastFillRequest != null) {
- mSecondaryProviderHandler.onFillRequest(mAssistReceiver.mLastFillRequest,
- flags);
- }
mLatencyBaseTime = SystemClock.elapsedRealtime();
boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted;
mPreviouslyFillDialogPotentiallyStarted = false;
@@ -4225,6 +4240,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
viewState.setCurrentValue(value);
}
+ if (shouldRequestSecondaryProvider(flags)) {
+ if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+ id, viewState, flags)) {
+ Slog.v(TAG, "Started a new fill request for secondary provider.");
+ return;
+ }
+ // If the ViewState is ready to be displayed, onReady() will be called.
+ viewState.update(value, virtualBounds, flags);
+
+ // return here because primary provider logic is not applicable.
+ return;
+ }
+
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
return;
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index b0bb9ec66f46..fec5aa531cdd 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -162,6 +162,11 @@ final class ViewState {
return mPrimaryFillResponse;
}
+ @Nullable
+ FillResponse getSecondaryResponse() {
+ return mSecondaryFillResponse;
+ }
+
void setResponse(FillResponse response) {
setResponse(response, /* isPrimary= */ true);
}
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index acb5911c8868..d08a97e2a573 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,7 +19,13 @@ java_library_static {
defaults: ["platform_service_defaults"],
srcs: [":services.backup-sources"],
libs: ["services.core"],
- static_libs: ["app-compat-annotations", "backup_flags_lib"],
+ static_libs: [
+ "app-compat-annotations",
+ "backup_flags_lib",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
aconfig_declarations {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 550e17be276d..2bfdd0a7c819 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -31,4 +31,7 @@ java_library_static {
"virtualdevice_flags_lib",
"virtual_camera_service_aidl-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index db40fc495cab..6c77018de27b 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.os.Binder.getCallingUid;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
@@ -41,6 +42,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.os.Binder;
+import android.os.Process;
import android.util.Log;
import android.util.Slog;
@@ -80,6 +82,11 @@ public final class PackageUtils {
static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
+ // Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP
+ if (getCallingUid() == Process.SYSTEM_UID) {
+ return;
+ }
+
String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index df74770fdc0f..62c670317f5f 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -130,7 +130,7 @@ public class CompanionTransportManager {
synchronized (mTransports) {
for (int i = 0; i < associationIds.length; i++) {
if (mTransports.contains(associationIds[i])) {
- mTransports.get(associationIds[i]).requestForResponse(message, data);
+ mTransports.get(associationIds[i]).sendMessage(message, data);
}
}
}
@@ -220,7 +220,7 @@ public class CompanionTransportManager {
if (transport == null) {
return CompletableFuture.failedFuture(new IOException("Missing transport"));
}
- return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+ return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 32d4061db1f7..22b18ac9653b 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -16,6 +16,9 @@
package com.android.server.companion.transport;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
@@ -80,6 +83,10 @@ public abstract class Transport {
return (message & 0xFF000000) == 0x33000000;
}
+ private static boolean isOneway(int message) {
+ return (message & 0xFF000000) == 0x43000000;
+ }
+
@GuardedBy("mPendingRequests")
protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
new SparseArray<>();
@@ -134,6 +141,42 @@ public abstract class Transport {
protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException;
+ /**
+ * Send a message using this transport. If the message was a request, then the returned Future
+ * object will complete successfully only if the remote device both received and processed it
+ * as expected. If the message was a send-and-forget type message, then the Future object will
+ * resolve successfully immediately (with null) upon sending the message.
+ *
+ * @param message the message type
+ * @param data the message payload
+ * @return Future object containing the result of the sent message.
+ */
+ public Future<byte[]> sendMessage(int message, byte[] data) {
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+ if (isOneway(message)) {
+ return sendAndForget(message, data);
+ } else if (isRequest(message)) {
+ return requestForResponse(message, data);
+ } else {
+ Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message));
+ pending.completeExceptionally(new IllegalArgumentException(
+ "The message being sent must be either a one-way or a request."
+ ));
+ }
+ return pending;
+ }
+
+ /**
+ * @deprecated Method was renamed to sendMessage(int, byte[]) to support both
+ * send-and-forget type messages as well as wait-for-response type messages.
+ *
+ * @param message request message type
+ * @param data the message payload
+ * @return future object containing the result of the request.
+ *
+ * @see #sendMessage(int, byte[])
+ */
+ @Deprecated
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
final int sequence = mNextSequence.incrementAndGet();
@@ -154,6 +197,20 @@ public abstract class Transport {
return pending;
}
+ private Future<byte[]> sendAndForget(int message, byte[]data) {
+ if (DEBUG) Slog.d(TAG, "Sending a one-way message");
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+
+ try {
+ sendMessage(message, -1, data);
+ pending.complete(null);
+ } catch (IOException e) {
+ pending.completeExceptionally(e);
+ }
+
+ return pending;
+ }
+
protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
if (DEBUG) {
@@ -162,7 +219,9 @@ public abstract class Transport {
+ " from association " + mAssociationId);
}
- if (isRequest(message)) {
+ if (isOneway(message)) {
+ processOneway(message, data);
+ } else if (isRequest(message)) {
try {
processRequest(message, sequence, data);
} catch (IOException e) {
@@ -175,6 +234,21 @@ public abstract class Transport {
}
}
+ private void processOneway(int message, byte[] data) {
+ switch (message) {
+ case MESSAGE_ONEWAY_PING:
+ case MESSAGE_ONEWAY_FROM_WEARABLE:
+ case MESSAGE_ONEWAY_TO_WEARABLE: {
+ callback(message, data);
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message));
+ break;
+ }
+ }
+ }
+
private void processRequest(int message, int sequence, byte[] data)
throws IOException {
switch (message) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5111b08a1812..dd001ec7da27 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -212,6 +212,9 @@ java_library_static {
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_genrule {
@@ -230,6 +233,9 @@ java_genrule {
java_library {
name: "services.core",
static_libs: ["services.core.priorityboosted"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_host {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efba1c89..08093c0c037f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@ public abstract class PackageManagerInternal {
*/
@NonNull
public abstract PackageArchiver getPackageArchiver();
+
+ /**
+ * Returns true if the device is upgrading from an SDK version lower than the one specified.
+ */
+ public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c258370dc12b..e289a56f5dc5 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@ per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3ae5527153b7..96b1650d9575 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@ public final class ActiveServices {
@Retention(RetentionPolicy.SOURCE)
@interface FgsStopReason {}
+ /**
+ * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+ * except:
+ * <ul>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+ * </ul>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@ public final class ActiveServices {
}
}
+ private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+ r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+ : REASON_DENIED;
+ if (Flags.fgsBootCompleted()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+ && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+ // Filter through types
+ return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+ }
+ // Not BOOT_COMPLETED
+ return true;
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName,
int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@ public final class ActiveServices {
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
@@ -8303,7 +8339,7 @@ public final class ActiveServices {
r.mFgsNotificationShown,
durationMs,
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
fgsTypeCheckCode,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3ce91c8a2b7b..1c3c21c63b57 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
@@ -73,6 +79,9 @@ final class ActivityManagerConstants extends ContentObserver {
= "fgservice_screen_on_before_time";
private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
= "fgservice_screen_on_after_time";
+
+ private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@ final class ActivityManagerConstants extends ContentObserver {
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+ private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+ | FOREGROUND_SERVICE_TYPE_HEALTH
+ | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ | FOREGROUND_SERVICE_TYPE_LOCATION;
+
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -225,7 +243,7 @@ final class ActivityManagerConstants extends ContentObserver {
/**
* The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
*/
- private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
+ private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
@@ -446,6 +464,9 @@ final class ActivityManagerConstants extends ContentObserver {
// on until we will stop reporting it.
public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
+ // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+ public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
// to avoid thrashing of provider processes under low memory situations.
@@ -1450,6 +1471,8 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+ FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+ DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -2091,6 +2114,8 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+ pw.print(" "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+ pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a80d2fd32593..f8451fda964f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -164,6 +164,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.MemoryStatUtil.hasMemcg;
import static com.android.server.am.ProcessList.ProcStartHandler;
+import static com.android.server.flags.Flags.disableSystemCompaction;
import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
@@ -8564,8 +8565,10 @@ public class ActivityManagerService extends IActivityManager.Stub
final long now = SystemClock.uptimeMillis();
final long timeSinceLastIdle = now - mLastIdleTime;
- // Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ if (!disableSystemCompaction()) {
+ // Compact all non-zygote processes to freshen up the page cache.
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
+ }
final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
mLastIdleTime = now;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index ae0cd65b2770..848a2b004f25 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else if (opt.equals("--dismiss-keyguard-if-insecure")
|| opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
+ } else if (opt.equals("--allow-fgs-start-reason")) {
+ final int reasonCode = Integer.parseInt(getNextArgRequired());
+ mBroadcastOptions = BroadcastOptions.makeBasic();
+ mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode,
+ "");
} else {
return false;
}
@@ -2238,6 +2246,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println("Performing idle maintenance...");
mInterface.sendIdleJobTrigger();
+ mInternal.performIdleMaintenance();
return 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b845a61..78a2ecb8fba1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -129,14 +129,6 @@ public class ActivityManagerUtils {
}
/**
- * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
- * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
- */
- public static int hashComponentNameForAtom(String shortInstanceName) {
- return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
- }
-
- /**
* Helper method to log an unsafe intent event.
*/
public static void logUnsafeIntentEvent(int event, int callingUid,
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 05303f6fef63..b68572fa5c1b 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -519,7 +519,7 @@ public class ForegroundServiceTypeLoggerModule {
r.mFgsNotificationShown,
0, // durationMs
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
0,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e57206ef9bc4..9a9d4eece6d2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2482,7 +2482,6 @@ public final class ProcessList {
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
app.getDisabledCompatChanges(),
- bindOverrideSysprops,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3e1edf2a4876..d0d647c7a669 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -141,6 +141,7 @@ public class SettingsToPropertiesMapper {
"context_hub",
"core_experiments_team_internal",
"core_graphics",
+ "core_libraries",
"dck_framework",
"devoptions_settings",
"game",
@@ -178,6 +179,7 @@ public class SettingsToPropertiesMapper {
"text",
"threadnetwork",
"tv_system_ui",
+ "usb",
"vibrator",
"virtual_devices",
"wallet_integration",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index badd7f085e56..2b81dbcb6fb2 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2059,9 +2059,6 @@ class UserController implements Handler.Callback {
mTargetUserId = targetUserId;
userSwitchUiEnabled = mUserSwitchUiEnabled;
}
- if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
- mInjector.setHasTopUi(true);
- }
if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
@@ -2130,9 +2127,6 @@ class UserController implements Handler.Callback {
}
private void endUserSwitch() {
- if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
- mInjector.setHasTopUi(false);
- }
final int nextUserId;
synchronized (mLock) {
nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3816,15 +3810,6 @@ class UserController implements Handler.Callback {
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void setHasTopUi(boolean hasTopUi) {
- try {
- Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi);
- mService.setHasTopUi(hasTopUi);
- } catch (RemoteException e) {
- Slogf.e(TAG, "Failed to allow using all CPU cores", e);
- }
- }
-
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b3fb9c947ca4..8b7e56ec410d 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.server.app.Flags.gameDefaultFrameRate;
+import static android.server.app.Flags.disableGameModeWhenAppTop;
import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver;
import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling;
@@ -181,7 +182,9 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Nullable
final MyUidObserver mUidObserver;
@GuardedBy("mUidObserverLock")
- private final Set<Integer> mForegroundGameUids = new HashSet<>();
+ private final Set<Integer> mGameForegroundUids = new HashSet<>();
+ @GuardedBy("mUidObserverLock")
+ private final Set<Integer> mNonGameForegroundUids = new HashSet<>();
private final GameManagerServiceSystemPropertiesWrapper mSysProps;
private float mGameDefaultFrameRateValue;
@@ -238,12 +241,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-1, -1);
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
mGameServiceController = new GameServiceController(
context, BackgroundThread.getExecutor(),
- new GameServiceProviderSelectorImpl(
- context.getResources(),
- context.getPackageManager()),
+ new GameServiceProviderSelectorImpl(context.getResources(), mPackageManager),
new GameServiceProviderInstanceFactoryImpl(context));
} else {
mGameServiceController = null;
@@ -2245,7 +2246,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
// Update all foreground games' frame rate.
synchronized (mUidObserverLock) {
- for (int uid : mForegroundGameUids) {
+ for (int uid : mGameForegroundUids) {
setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate(isEnabled));
}
}
@@ -2261,31 +2262,44 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUidGone(int uid, boolean disabled) {
synchronized (mUidObserverLock) {
- disableGameMode(uid);
+ handleUidMovedOffTop(uid);
}
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- synchronized (mUidObserverLock) {
-
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- disableGameMode(uid);
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_TOP:
+ handleUidMovedToTop(uid);
return;
- }
+ default:
+ handleUidMovedOffTop(uid);
+ }
+ }
- final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages == null || packages.length == 0) {
- return;
- }
+ private void handleUidMovedToTop(int uid) {
+ final String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null || packages.length == 0) {
+ return;
+ }
- final int userId = mContext.getUserId();
- if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
+ final int userId = mContext.getUserId();
+ final boolean isNotGame = Arrays.stream(packages).noneMatch(
+ p -> isPackageGame(p, userId));
+ synchronized (mUidObserverLock) {
+ if (isNotGame) {
+ if (disableGameModeWhenAppTop()) {
+ if (!mGameForegroundUids.isEmpty() && mNonGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode OFF (first non-game in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ mNonGameForegroundUids.add(uid);
+ }
return;
}
-
- if (mForegroundGameUids.isEmpty()) {
- Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode ON (first game in foreground)");
mPowerManagerInternal.setPowerMode(Mode.GAME, true);
}
final boolean isGameDefaultFrameRateDisabled =
@@ -2293,22 +2307,26 @@ public final class GameManagerService extends IGameManagerService.Stub {
PROPERTY_DEBUG_GFX_GAME_DEFAULT_FRAME_RATE_DISABLED, false);
setGameDefaultFrameRateOverride(uid,
getGameDefaultFrameRate(!isGameDefaultFrameRateDisabled));
- mForegroundGameUids.add(uid);
+ mGameForegroundUids.add(uid);
}
}
- private void disableGameMode(int uid) {
+ private void handleUidMovedOffTop(int uid) {
synchronized (mUidObserverLock) {
- if (!mForegroundGameUids.contains(uid)) {
- return;
- }
- mForegroundGameUids.remove(uid);
- if (!mForegroundGameUids.isEmpty()) {
- return;
+ if (mGameForegroundUids.contains(uid)) {
+ mGameForegroundUids.remove(uid);
+ if (mGameForegroundUids.isEmpty() && (!disableGameModeWhenAppTop()
+ || mNonGameForegroundUids.isEmpty())) {
+ Slog.v(TAG, "Game power mode OFF (no games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+ }
+ } else if (disableGameModeWhenAppTop() && mNonGameForegroundUids.contains(uid)) {
+ mNonGameForegroundUids.remove(uid);
+ if (mNonGameForegroundUids.isEmpty() && !mGameForegroundUids.isEmpty()) {
+ Slog.v(TAG, "Game power mode ON (only games in foreground)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+ }
}
- Slog.v(TAG,
- "Game power mode OFF (process remomved or state changed to background)");
- mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
}
}
diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig
index f2e4783bd9eb..0673013bdc44 100644
--- a/services/core/java/com/android/server/app/flags.aconfig
+++ b/services/core/java/com/android/server/app/flags.aconfig
@@ -6,4 +6,11 @@ flag {
description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
bug: "286084594"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "disable_game_mode_when_app_top"
+ namespace: "game"
+ description: "Disable game power mode when a non-game app is also top and visible"
+ bug: "299295925"
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 56ae2bfcb38f..37fe38924037 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -33,6 +33,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
@@ -43,6 +44,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -116,6 +118,7 @@ import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.BluetoothProfileConnectionInfo;
+import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioModeDispatcher;
@@ -605,6 +608,7 @@ public class AudioService extends IAudioService.Stub
};
private final boolean mUseFixedVolume;
+ private final boolean mRingerModeAffectsAlarm;
private final boolean mUseVolumeGroupAliases;
// If absolute volume is supported in AVRCP device
@@ -1298,6 +1302,9 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mRingerModeAffectsAlarm = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_audio_ringer_mode_affects_alarm_stream);
+
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -4513,6 +4520,8 @@ public class AudioService extends IAudioService.Stub
+ bluetoothMacAddressAnonymization());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
+ pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:"
+ + enableFadeManagerConfiguration());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -7015,6 +7024,19 @@ public class AudioService extends IAudioService.Stub
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
}
+ if (ringerModeAffectsAlarm()) {
+ if (mRingerModeAffectsAlarm) {
+ boolean muteAlarmWithRinger =
+ mSettings.getGlobalInt(mContentResolver,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
+ /* def= */ 0) != 0;
+ if (muteAlarmWithRinger) {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_ALARM);
+ } else {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_ALARM);
+ }
+ }
+ }
if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
mSettings.putSystemIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -9674,6 +9696,8 @@ public class AudioService extends IAudioService.Stub
Settings.Global.ZEN_MODE), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
@@ -12614,6 +12638,47 @@ public class AudioService extends IAudioService.Stub
}
/**
+ * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int setFadeManagerConfigurationForFocusLoss(
+ @NonNull FadeManagerConfiguration fmcForFocusLoss) {
+ super.setFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(fmcForFocusLoss,
+ "Fade manager config for focus loss cannot be null");
+ validateFadeManagerConfiguration(fmcForFocusLoss);
+
+ return mPlaybackMonitor.setFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS,
+ fmcForFocusLoss);
+ }
+
+ /**
+ * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int clearFadeManagerConfigurationForFocusLoss() {
+ super.clearFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.clearFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
+ * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() {
+ super.getFadeManagerConfigurationForFocusLoss_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+
+ return mPlaybackMonitor.getFadeManagerConfiguration(AudioManager.AUDIOFOCUS_LOSS);
+ }
+
+ /**
* @see AudioManager#getHalVersion
*/
public @Nullable AudioHalVersionInfo getHalVersion() {
@@ -12814,6 +12879,19 @@ public class AudioService extends IAudioService.Stub
}
}
+ private void ensureFadeManagerConfigIsEnabled() {
+ Preconditions.checkState(enableFadeManagerConfiguration(),
+ "Fade manager configuration not supported");
+ }
+
+ private void validateFadeManagerConfiguration(FadeManagerConfiguration fmc) {
+ // validate permission of audio attributes
+ List<AudioAttributes> attrs = fmc.getAudioAttributesWithVolumeShaperConfigs();
+ for (int index = 0; index < attrs.size(); index++) {
+ validateAudioAttributesUsage(attrs.get(index));
+ }
+ }
+
//======================
// Audio policy callbacks from AudioSystem for dynamic policies
//======================
@@ -13114,6 +13192,7 @@ public class AudioService extends IAudioService.Stub
+ "could not link to " + projection + " binder death", e);
}
}
+
int status = connectMixes();
if (status != AudioSystem.SUCCESS) {
release();
@@ -13471,6 +13550,43 @@ public class AudioService extends IAudioService.Stub
}
}
+ /**
+ * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy,
+ * List, FadeManagerConfiguration)}
+ */
+ @android.annotation.EnforcePermission(
+ android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis,
+ FadeManagerConfiguration transientFadeMgrConfig) {
+ super.dispatchFocusChangeWithFade_enforcePermission();
+ ensureFadeManagerConfigIsEnabled();
+ Objects.requireNonNull(afi, "AudioFocusInfo cannot be null");
+ Objects.requireNonNull(pcb, "AudioPolicy callback cannot be null");
+ Objects.requireNonNull(otherActiveAfis,
+ "Other active AudioFocusInfo list cannot be null");
+ if (transientFadeMgrConfig != null) {
+ validateFadeManagerConfiguration(transientFadeMgrConfig);
+ }
+
+ synchronized (mAudioPolicies) {
+ Preconditions.checkState(mAudioPolicies.containsKey(pcb.asBinder()),
+ "Unregistered AudioPolicy for focus dispatch with fade");
+
+ // set the transient fade manager config to be used for handling this focus change
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.setTransientFadeManagerConfiguration(focusChange,
+ transientFadeMgrConfig);
+ }
+ int status = mMediaFocusControl.dispatchFocusChangeWithFade(afi, focusChange,
+ otherActiveAfis);
+
+ if (transientFadeMgrConfig != null) {
+ mPlaybackMonitor.clearTransientFadeManagerConfiguration(focusChange);
+ }
+ return status;
+ }
+ }
//======================
// Audioserver state dispatch
diff --git a/services/core/java/com/android/server/audio/FadeConfigurations.java b/services/core/java/com/android/server/audio/FadeConfigurations.java
index 2e27c7697e82..37ecf0bdcc7a 100644
--- a/services/core/java/com/android/server/audio/FadeConfigurations.java
+++ b/services/core/java/com/android/server/audio/FadeConfigurations.java
@@ -16,13 +16,22 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Class to encapsulate configurations used for fading players
@@ -69,51 +78,229 @@ public final class FadeConfigurations {
private static final int INVALID_UID = -1;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mDefaultFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mUpdatedFadeManagerConfig;
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mTransientFadeManagerConfig;
+ /** active fade manager is one of: transient > updated > default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration mActiveFadeManagerConfig;
+
+ /**
+ * Sets the custom fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration
+ * feature is disabled)
+ */
+ public int setFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Fade manager configuration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set fade manager configuration is cleared
+ * or {@link AudioManager#ERROR} otherwise (example, when fade manager configuration feature
+ * is disabled)
+ */
+ public int clearFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mUpdatedFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return {@code null} if feature is disabled, or the custom fade manager configuration if set,
+ * or default fade manager configuration if not set.
+ */
+ @Nullable
+ public FadeManagerConfiguration getFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+
+ synchronized (mLock) {
+ return mActiveFadeManagerConfig;
+ }
+ }
+
+ /**
+ * Sets the transient fade manager configuration
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting custom fade manager configuration succeeds
+ * or {@link AudioManager#ERROR} otherwise (example - when fade manager configuration is
+ * disabled)
+ */
+ public int setTransientFadeManagerConfiguration(
+ @NonNull FadeManagerConfiguration fadeManagerConfig) {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = Objects.requireNonNull(fadeManagerConfig,
+ "Transient FadeManagerConfiguration cannot be null");
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if previously set transient fade manager configuration
+ * is cleared or {@link AudioManager#ERROR} otherwise (example - when fade manager
+ * configuration is disabled)
+ */
+ public int clearTransientFadeManagerConfiguration() {
+ if (!enableFadeManagerConfiguration()) {
+ return AudioManager.ERROR;
+ }
+ synchronized (mLock) {
+ mTransientFadeManagerConfig = null;
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ /**
+ * Query if fade should be enforecd on players
+ *
+ * @return {@code true} if fade is enabled or using default configurations, {@code false}
+ * otherwise.
+ */
+ public boolean isFadeEnabled() {
+ if (!enableFadeManagerConfiguration()) {
+ return true;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().isFadeEnabled();
+ }
+ }
+
/**
* Query {@link android.media.AudioAttributes.AttributeUsage usages} that are allowed to
* fade
+ *
* @return list of {@link android.media.AudioAttributes.AttributeUsage}
*/
@NonNull
public List<Integer> getFadeableUsages() {
- return DEFAULT_FADEABLE_USAGES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getFadeableUsages()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioAttributes.AttributeContentType content types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioAttributes.AttributeContentType}
*/
@NonNull
public List<Integer> getUnfadeableContentTypes() {
- return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableContentTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Query {@link android.media.AudioPlaybackConfiguration.PlayerType player types} that are
* exempted from fade enforcement
+ *
* @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
*/
@NonNull
public List<Integer> getUnfadeablePlayerTypes() {
- return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return an empty list instead
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeablePlayerTypes()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied
* for the fade-out
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return {@link android.media.VolumeShaper.Configuration} for the
- * {@link android.media.AudioAttributes.AttributeUsage} or default volume shaper if not
- * configured
+ * {@link android.media.AudioAttributes} or default volume shaper if not configured
*/
@NonNull
public VolumeShaper.Configuration getFadeOutVolumeShaperConfig(@NonNull AudioAttributes aa) {
- return DEFAULT_FADEOUT_VSHAPE;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEOUT_VSHAPE;
+ }
+ return getOptimalFadeOutVolShaperConfig(aa);
+ }
+
+ /**
+ * Get the {@link android.media.VolumeShaper.Configuration} configuration to be applied for the
+ * fade in
+ *
+ * @param aa The {@link android.media.AudioAttributes}
+ * @return {@link android.media.VolumeShaper.Configuration} for the
+ * {@link android.media.AudioAttributes} or {@code null} otherwise
+ */
+ @Nullable
+ public VolumeShaper.Configuration getFadeInVolumeShaperConfig(@NonNull AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ return null;
+ }
+ return getOptimalFadeInVolShaperConfig(aa);
}
+
/**
* Get the duration to fade out a player of type usage
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return duration in milliseconds for the
* {@link android.media.AudioAttributes} or default duration if not configured
@@ -122,22 +309,73 @@ public final class FadeConfigurations {
if (!isFadeable(aa, INVALID_UID, AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN)) {
return 0;
}
- return DEFAULT_FADE_OUT_DURATION_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADE_OUT_DURATION_MS;
+ }
+ return getOptimalFadeOutDuration(aa);
}
/**
- * Get the delay to fade in offending players that do not stop after losing audio focus.
+ * Get the delay to fade in offending players that do not stop after losing audio focus
+ *
* @param aa The {@link android.media.AudioAttributes}
* @return delay in milliseconds for the
* {@link android.media.AudioAttributes.Attribute} or default delay if not configured
*/
public long getDelayFadeInOffenders(@NonNull AudioAttributes aa) {
- return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+ }
+
+ synchronized (mLock) {
+ return getUpdatedFadeManagerConfigLocked().getFadeInDelayForOffenders();
+ }
+ }
+
+ /**
+ * Query {@link android.media.AudioAttributes} that are exempted from fade enforcement
+ *
+ * @return list of {@link android.media.AudioAttributes}
+ */
+ @NonNull
+ public List<AudioAttributes> getUnfadeableAudioAttributes() {
+ // unfadeable audio attributes is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled()
+ ? fadeManagerConfig.getUnfadeableAudioAttributes() : Collections.EMPTY_LIST;
+ }
+ }
+
+ /**
+ * Query uids that are exempted from fade enforcement
+ *
+ * @return list of uids
+ */
+ @NonNull
+ public List<Integer> getUnfadeableUids() {
+ // unfadeable uids is only supported with fade manager configurations
+ if (!enableFadeManagerConfiguration()) {
+ return Collections.EMPTY_LIST;
+ }
+
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // when fade is not enabled, return empty list
+ return fadeManagerConfig.isFadeEnabled() ? fadeManagerConfig.getUnfadeableUids()
+ : Collections.EMPTY_LIST;
+ }
}
/**
* Check if it is allowed to fade for the given {@link android.media.AudioAttributes},
- * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config.
+ * client uid and {@link android.media.AudioPlaybackConfiguration.PlayerType} config
+ *
* @param aa The {@link android.media.AudioAttributes}
* @param uid The uid of the client owning the player
* @param playerType The {@link android.media.AudioPlaybackConfiguration.PlayerType}
@@ -145,36 +383,173 @@ public final class FadeConfigurations {
*/
public boolean isFadeable(@NonNull AudioAttributes aa, int uid,
@AudioPlaybackConfiguration.PlayerType int playerType) {
- if (isPlayerTypeUnfadeable(playerType)) {
- if (DEBUG) {
- Slog.i(TAG, "not fadeable: player type:" + playerType);
+ synchronized (mLock) {
+ if (isPlayerTypeUnfadeableLocked(playerType)) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: player type:" + playerType);
+ }
+ return false;
}
- return false;
+ if (isContentTypeUnfadeableLocked(aa.getContentType())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ }
+ return false;
+ }
+ if (!isUsageFadeableLocked(aa.getSystemUsage())) {
+ if (DEBUG) {
+ Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ }
+ return false;
+ }
+ // new configs using fade manager configuration
+ if (isUnfadeableForFadeMgrConfigLocked(aa, uid)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /** Tries to get the fade out volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeOutVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeOutVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeOutVolumeShaperConfigForUsage(
+ aa.getSystemUsage());
}
- if (isContentTypeUnfadeable(aa.getContentType())) {
+ }
+
+ /** Tries to get the fade in volume shaper config closest to the audio attributes */
+ private VolumeShaper.Configuration getOptimalFadeInVolShaperConfig(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if the specific audio attributes has a volume shaper config defined
+ VolumeShaper.Configuration volShaperConfig =
+ fadeManagerConfig.getFadeInVolumeShaperConfigForAudioAttributes(aa);
+ if (volShaperConfig != null) {
+ return volShaperConfig;
+ }
+
+ // get the volume shaper config for usage
+ // for fadeable usages, this should never return null
+ return fadeManagerConfig.getFadeInVolumeShaperConfigForUsage(aa.getSystemUsage());
+ }
+ }
+
+ /** Tries to get the duration closest to the audio attributes */
+ private long getOptimalFadeOutDuration(AudioAttributes aa) {
+ synchronized (mLock) {
+ FadeManagerConfiguration fadeManagerConfig = getUpdatedFadeManagerConfigLocked();
+ // check if specific audio attributes has a duration defined
+ long duration = fadeManagerConfig.getFadeOutDurationForAudioAttributes(aa);
+ if (duration != FadeManagerConfiguration.DURATION_NOT_SET) {
+ return duration;
+ }
+
+ // get the duration for usage
+ // for fadeable usages, this should never return DURATION_NOT_SET
+ return fadeManagerConfig.getFadeOutDurationForUsage(aa.getSystemUsage());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnfadeableForFadeMgrConfigLocked(AudioAttributes aa, int uid) {
+ if (isAudioAttributesUnfadeableLocked(aa)) {
if (DEBUG) {
- Slog.i(TAG, "not fadeable: content type:" + aa.getContentType());
+ Slog.i(TAG, "not fadeable: aa:" + aa);
}
- return false;
+ return true;
}
- if (!isUsageFadeable(aa.getUsage())) {
+ if (isUidUnfadeableLocked(uid)) {
if (DEBUG) {
- Slog.i(TAG, "not fadeable: usage:" + aa.getUsage());
+ Slog.i(TAG, "not fadeable: uid:" + uid);
}
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUsageFadeableLocked(int usage) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_FADEABLE_USAGES.contains(usage);
+ }
+ return getUpdatedFadeManagerConfigLocked().isUsageFadeable(usage);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isContentTypeUnfadeableLocked(int contentType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_CONTENT_TYPES.contains(contentType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isContentTypeUnfadeable(contentType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isPlayerTypeUnfadeableLocked(int playerType) {
+ if (!enableFadeManagerConfiguration()) {
+ return DEFAULT_UNFADEABLE_PLAYER_TYPES.contains(playerType);
+ }
+ return getUpdatedFadeManagerConfigLocked().isPlayerTypeUnfadeable(playerType);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isAudioAttributesUnfadeableLocked(AudioAttributes aa) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable audio attributes, hence return false
return false;
}
- return true;
+ return getUpdatedFadeManagerConfigLocked().isAudioAttributesUnfadeable(aa);
}
- private boolean isUsageFadeable(int usage) {
- return getFadeableUsages().contains(usage);
+ @GuardedBy("mLock")
+ private boolean isUidUnfadeableLocked(int uid) {
+ if (!enableFadeManagerConfiguration()) {
+ // default fade configs do not support unfadeable uids, hence return false
+ return false;
+ }
+ return getUpdatedFadeManagerConfigLocked().isUidUnfadeable(uid);
}
- private boolean isContentTypeUnfadeable(int contentType) {
- return getUnfadeableContentTypes().contains(contentType);
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getUpdatedFadeManagerConfigLocked() {
+ if (mActiveFadeManagerConfig == null) {
+ mActiveFadeManagerConfig = getActiveFadeMgrConfigLocked();
+ }
+ return mActiveFadeManagerConfig;
}
- private boolean isPlayerTypeUnfadeable(int playerType) {
- return getUnfadeablePlayerTypes().contains(playerType);
+ /** Priority between fade manager configs: Transient > Updated > Default */
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getActiveFadeMgrConfigLocked() {
+ // below configs are arranged in the order of priority
+ // configs placed higher have higher priority
+ if (mTransientFadeManagerConfig != null) {
+ return mTransientFadeManagerConfig;
+ }
+
+ if (mUpdatedFadeManagerConfig != null) {
+ return mUpdatedFadeManagerConfig;
+ }
+
+ // default - must be the lowest priority
+ return getDefaultFadeManagerConfigLocked();
+ }
+
+ @GuardedBy("mLock")
+ private FadeManagerConfiguration getDefaultFadeManagerConfigLocked() {
+ if (mDefaultFadeManagerConfig == null) {
+ mDefaultFadeManagerConfig = new FadeManagerConfiguration.Builder().build();
+ }
+ return mDefaultFadeManagerConfig;
}
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 1171f97533c7..cbcd8f5974ac 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -16,21 +16,24 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Objects;
+import java.util.List;
+import java.util.Map;
/**
* Class to handle fading out players
@@ -40,14 +43,6 @@ public final class FadeOutManager {
public static final String TAG = "AS.FadeOutManager";
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
- private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
- new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
- .createIfNeeded()
- .build();
-
- // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
- private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
- new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
private final Object mLock = new Object();
@@ -57,16 +52,81 @@ public final class FadeOutManager {
@GuardedBy("mLock")
private final SparseArray<FadedOutApp> mUidToFadedAppsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private FadeConfigurations mFadeConfigurations;
+ private final FadeConfigurations mFadeConfigurations = new FadeConfigurations();
+
+ /**
+ * Sets the custom fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig custom fade manager configuration
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
- public FadeOutManager() {
- mFadeConfigurations = new FadeConfigurations();
+ /**
+ * Clears the fade manager configuration that was previously set with
+ * {@link #setFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearFadeManagerConfiguration();
+ }
}
- public FadeOutManager(FadeConfigurations fadeConfigurations) {
- mFadeConfigurations = Objects.requireNonNull(fadeConfigurations,
- "Fade configurations can not be null");
+ /**
+ * Returns the active fade manager configuration
+ *
+ * @return the {@link FadeManagerConfiguration}
+ */
+ FadeManagerConfiguration getFadeManagerConfiguration() {
+ return mFadeConfigurations.getFadeManagerConfiguration();
+ }
+
+ /**
+ * Sets the transient fade manager configuration to be used for player fade out and in
+ *
+ * @param fadeManagerConfig fade manager config that has higher priority than the existing
+ * fade manager configuration. This is expected to be transient.
+ * @return {@link AudioManager#SUCCESS} if setting fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int setTransientFadeManagerConfiguration(FadeManagerConfiguration fadeManagerConfig) {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.setTransientFadeManagerConfiguration(fadeManagerConfig);
+ }
+ }
+
+ /**
+ * Clears the transient fade manager configuration that was previously set with
+ * {@link #setTransientFadeManagerConfiguration(FadeManagerConfiguration)}
+ *
+ * @return {@link AudioManager#SUCCESS} if clearing fade manager config succeeded,
+ * {@link AudioManager#ERROR} otherwise
+ */
+ int clearTransientFadeManagerConfiguration() {
+ // locked to ensure the fade configs are not updated while faded app state is being updated
+ synchronized (mLock) {
+ return mFadeConfigurations.clearTransientFadeManagerConfiguration();
+ }
+ }
+
+ /**
+ * Query if fade is enblead and can be enforced on players
+ *
+ * @return {@code true} if fade is enabled, {@code false} otherwise.
+ */
+ boolean isFadeEnabled() {
+ return mFadeConfigurations.isFadeEnabled();
}
// TODO explore whether a shorter fade out would be a better UX instead of not fading out at all
@@ -128,7 +188,7 @@ public final class FadeOutManager {
}
}
- void fadeOutUid(int uid, ArrayList<AudioPlaybackConfiguration> players) {
+ void fadeOutUid(int uid, List<AudioPlaybackConfiguration> players) {
Slog.i(TAG, "fadeOutUid() uid:" + uid);
synchronized (mLock) {
if (!mUidToFadedAppsMap.contains(uid)) {
@@ -148,15 +208,31 @@ public final class FadeOutManager {
* @param uid the uid for the app to unfade out
* @param players map of current available players (so we can get an APC from piid)
*/
- void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void unfadeOutUid(int uid, Map<Integer, AudioPlaybackConfiguration> players) {
Slog.i(TAG, "unfadeOutUid() uid:" + uid);
synchronized (mLock) {
- final FadedOutApp fa = mUidToFadedAppsMap.get(uid);
+ FadedOutApp fa = mUidToFadedAppsMap.get(uid);
if (fa == null) {
return;
}
mUidToFadedAppsMap.remove(uid);
- fa.removeUnfadeAll(players);
+
+ if (!enableFadeManagerConfiguration()) {
+ fa.removeUnfadeAll(players);
+ return;
+ }
+
+ // since fade manager configs may have volume-shaper config per audio attributes,
+ // iterate through each palyer and gather respective configs for fade in
+ ArrayList<AudioPlaybackConfiguration> apcs = new ArrayList<>(players.values());
+ for (int index = 0; index < apcs.size(); index++) {
+ AudioPlaybackConfiguration apc = apcs.get(index);
+ VolumeShaper.Configuration config = mFadeConfigurations
+ .getFadeInVolumeShaperConfig(apc.getAudioAttributes());
+ fa.fadeInPlayer(apc, config);
+ }
+ // ideal case all players should be faded in
+ fa.clear();
}
}
@@ -209,16 +285,6 @@ public final class FadeOutManager {
}
}
- /**
- * Update fade configurations used for player fade operations
- * @param fadeConfigurations set of configs that define fade properties
- */
- void setFadeConfigurations(@NonNull FadeConfigurations fadeConfigurations) {
- synchronized (mLock) {
- mFadeConfigurations = fadeConfigurations;
- }
- }
-
void dump(PrintWriter pw) {
synchronized (mLock) {
for (int index = 0; index < mUidToFadedAppsMap.size(); index++) {
@@ -232,6 +298,15 @@ public final class FadeOutManager {
* Class to group players from a common app, that are faded out.
*/
private static final class FadedOutApp {
+ private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
+ new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
+ .createIfNeeded()
+ .build();
+
+ // like a PLAY_CREATE_IF_NEEDED operation but with a skip to the end of the ramp
+ private static final VolumeShaper.Operation PLAY_SKIP_RAMP =
+ new VolumeShaper.Operation.Builder(PLAY_CREATE_IF_NEEDED).setXOffset(1.0f).build();
+
private final int mUid;
// key -> piid; value -> volume shaper config applied
private final SparseArray<VolumeShaper.Configuration> mFadedPlayers = new SparseArray<>();
@@ -269,17 +344,9 @@ public final class FadeOutManager {
return;
}
if (apc.getPlayerProxy() != null) {
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(
- TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
- mFadedPlayers.put(piid, volShaper);
- } catch (Exception e) {
- Slog.e(TAG, "Error fading out player piid:" + piid
- + " uid:" + apc.getClientUid(), e);
- }
+ applyVolumeShaperInternal(apc, piid, volShaper,
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ mFadedPlayers.put(piid, volShaper);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
@@ -288,21 +355,13 @@ public final class FadeOutManager {
}
}
- void removeUnfadeAll(HashMap<Integer, AudioPlaybackConfiguration> players) {
+ void removeUnfadeAll(Map<Integer, AudioPlaybackConfiguration> players) {
for (int index = 0; index < mFadedPlayers.size(); index++) {
int piid = mFadedPlayers.keyAt(index);
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
- final VolumeShaper.Configuration volShaper = mFadedPlayers.valueAt(index);
- try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new EventLogger.StringEvent("unfading out piid:"
- + piid)).printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(volShaper,
- VolumeShaper.Operation.REVERSE);
- } catch (Exception e) {
- Slog.e(TAG, "Error unfading out player piid:" + piid + " uid:" + mUid, e);
- }
+ applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
+ VolumeShaper.Operation.REVERSE);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -314,8 +373,61 @@ public final class FadeOutManager {
mFadedPlayers.clear();
}
+ void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
+ @Nullable VolumeShaper.Configuration config) {
+ int piid = Integer.valueOf(apc.getPlayerInterfaceId());
+ // if not found, no need to fade in since it was never faded out
+ if (!mFadedPlayers.contains(piid)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Player (piid: " + piid + ") for uid (" + mUid
+ + ") is not faded out, no need to fade in");
+ }
+ return;
+ }
+
+ mFadedPlayers.remove(piid);
+ if (apc.getPlayerProxy() != null) {
+ applyVolumeShaperInternal(apc, piid, config,
+ config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Error fading in player piid:" + piid
+ + ", player not found for uid " + mUid);
+ }
+ }
+ }
+
+ void clear() {
+ if (mFadedPlayers.size() > 0) {
+ if (DEBUG) {
+ Slog.v(TAG, "Non empty faded players list being cleared! Faded out players:"
+ + mFadedPlayers);
+ }
+ }
+ // should the players be faded in irrespective?
+ mFadedPlayers.clear();
+ }
+
void removeReleased(@NonNull AudioPlaybackConfiguration apc) {
mFadedPlayers.delete(Integer.valueOf(apc.getPlayerInterfaceId()));
}
+
+ private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration config = volShaperConfig;
+ // when operation is reverse, use the fade out volume shaper config instead
+ if (operation.equals(VolumeShaper.Operation.REVERSE)) {
+ config = mFadedPlayers.get(piid);
+ }
+ try {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
+ .printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
+ + " operation:" + operation, e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 00c04ff12c89..f462539d5bbf 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -33,6 +33,7 @@ import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
import com.android.server.pm.UserManagerInternal;
import java.io.PrintWriter;
+import java.util.List;
/**
* @hide
@@ -534,6 +535,33 @@ public class FocusRequester {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
+ @GuardedBy("MediaFocusControl.mAudioFocusLock")
+ int dispatchFocusChangeWithFadeLocked(int focusChange, List<FocusRequester> otherActiveFrs) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ || focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mFocusLossFadeLimbo = false;
+ mFocusController.restoreVShapedPlayers(this);
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+ && mFocusController.shouldEnforceFade()) {
+ for (int index = 0; index < otherActiveFrs.size(); index++) {
+ // candidate for fade-out before a receiving a loss
+ if (mFocusController.fadeOutPlayers(otherActiveFrs.get(index), /* loser= */ this)) {
+ // active players are being faded out, delay the dispatch of focus loss
+ // mark this instance as being faded so it's not released yet as the focus loss
+ // will be dispatched later, it is now in limbo mode
+ mFocusLossFadeLimbo = true;
+ mFocusController.postDelayedLossAfterFade(this,
+ mFocusController.getFadeOutDurationOnFocusLossMillis(
+ this.getAudioAttributes()));
+ return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ }
+ }
+ }
+ return dispatchFocusChange(focusChange);
+ }
+
void dispatchFocusResultFromExtPolicy(int requestResult) {
final IAudioFocusDispatcher fd = mFocusDispatcher;
if (fd == null) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 58f5d5e21cf0..0df0006c7be3 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -195,6 +197,15 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
return mFocusEnforcer.getFadeInDelayForOffendersMillis(aa);
}
+
+ @Override
+ public boolean shouldEnforceFade() {
+ if (!enableFadeManagerConfiguration()) {
+ return ENFORCE_FADEOUT_FOR_FOCUS_LOSS;
+ }
+
+ return mFocusEnforcer.shouldEnforceFade();
+ }
//==========================================================================================
// AudioFocus
//==========================================================================================
@@ -861,14 +872,17 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
return;
}
}
- final FocusRequester fr;
- if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
- if (fr != null) {
- fr.dispatchFocusResultFromExtPolicy(requestResult);
+ synchronized (mAudioFocusLock) {
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ requestResult == AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+ if (fr != null) {
+ fr.dispatchFocusResultFromExtPolicy(requestResult);
+ // if fade is enabled for external focus policies, apply it when setting
+ // focus result as well
+ if (enableFadeManagerConfiguration()) {
+ fr.handleFocusGainFromRequest(requestResult);
+ }
+ }
}
}
@@ -902,22 +916,78 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
+ afi.getClientId());
}
synchronized (mAudioFocusLock) {
- if (mFocusPolicy == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
+ FocusRequester fr = getFocusRequesterLocked(afi.getClientId(),
+ /* shouldRemove= */ focusChange == AudioManager.AUDIOFOCUS_LOSS);
+ if (fr == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- final FocusRequester fr;
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
- fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
- } else {
- fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
- }
+ return fr.dispatchFocusChange(focusChange);
+ }
+ }
+
+ int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange,
+ List<AudioFocusInfo> otherActiveAfis) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatchFocusChangeWithFade " + AudioManager.audioFocusToString(focusChange)
+ + " to afi client=" + afi.getClientId()
+ + " other active afis=" + otherActiveAfis);
+ }
+
+ synchronized (mAudioFocusLock) {
+ String clientId = afi.getClientId();
+ // do not remove the entry since it can be posted for fade
+ FocusRequester fr = getFocusRequesterLocked(clientId, /* shouldRemove= */ false);
if (fr == null) {
- if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
+ }
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
- return fr.dispatchFocusChange(focusChange);
+
+ // convert other AudioFocusInfo to corresponding FocusRequester
+ ArrayList<FocusRequester> otherActiveFrs = new ArrayList<>();
+ for (int index = 0; index < otherActiveAfis.size(); index++) {
+ FocusRequester otherFr = getFocusRequesterLocked(
+ otherActiveAfis.get(index).getClientId(), /* shouldRemove= */ false);
+ if (otherFr == null) {
+ continue;
+ }
+ otherActiveFrs.add(otherFr);
+ }
+
+ int status = fr.dispatchFocusChangeWithFadeLocked(focusChange, otherActiveFrs);
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_DELAYED
+ && focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ mFocusOwnersForFocusPolicy.remove(clientId);
+ }
+
+ return status;
+ }
+ }
+
+ @GuardedBy("mAudioFocusLock")
+ private FocusRequester getFocusRequesterLocked(String clientId, boolean shouldRemove) {
+ if (mFocusPolicy == null) {
+ if (DEBUG) {
+ Log.v(TAG, "> failed: no focus policy");
+ }
+ return null;
+ }
+
+ FocusRequester fr;
+ if (shouldRemove) {
+ fr = mFocusOwnersForFocusPolicy.remove(clientId);
+ } else {
+ fr = mFocusOwnersForFocusPolicy.get(clientId);
+ }
+
+ if (fr == null && DEBUG) {
+ Log.v(TAG, "> failed: no such focus requester known");
}
+ return fr;
}
private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index bc9b9b4b1c88..e69fbbd2a083 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -38,6 +38,7 @@ import android.media.AudioPlaybackConfiguration;
import android.media.AudioPlaybackConfiguration.FormatInfo;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
+import android.media.FadeManagerConfiguration;
import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
import android.media.VolumeShaper;
@@ -156,8 +157,7 @@ public final class PlaybackActivityMonitor
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- private final FadeOutManager mFadeOutManager;
-
+ private final FadeOutManager mFadeOutManager = new FadeOutManager();
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
@@ -167,7 +167,6 @@ public final class PlaybackActivityMonitor
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
initEventHandler();
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
}
//=================================================================
@@ -971,6 +970,12 @@ public final class PlaybackActivityMonitor
return mFadeOutManager.getFadeInDelayForOffendersMillis(aa);
}
+ @Override
+ public boolean shouldEnforceFade() {
+ return mFadeOutManager.isFadeEnabled();
+ }
+
+
//=================================================================
// Track playback activity listeners
@@ -1010,6 +1015,27 @@ public final class PlaybackActivityMonitor
}
}
+ int setFadeManagerConfiguration(int focusType, FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearFadeManagerConfiguration();
+ }
+
+ FadeManagerConfiguration getFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.getFadeManagerConfiguration();
+ }
+
+ int setTransientFadeManagerConfiguration(int focusType,
+ FadeManagerConfiguration fadeMgrConfig) {
+ return mFadeOutManager.setTransientFadeManagerConfiguration(fadeMgrConfig);
+ }
+
+ int clearTransientFadeManagerConfiguration(int focusType) {
+ return mFadeOutManager.clearTransientFadeManagerConfiguration();
+ }
+
/**
* Inner class to track clients that want to be notified of playback updates
*/
@@ -1337,6 +1363,38 @@ public final class PlaybackActivityMonitor
}
}
+ static final class FadeEvent extends EventLogger.Event {
+ private final int mPlayerIId;
+ private final int mPlayerType;
+ private final int mClientUid;
+ private final int mClientPid;
+ private final AudioAttributes mPlayerAttr;
+ private final VolumeShaper.Configuration mVShaper;
+ private final VolumeShaper.Operation mVOperation;
+
+ FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
+ VolumeShaper.Operation vOperation) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ mPlayerAttr = apc.getAudioAttributes();
+ mPlayerType = apc.getPlayerType();
+ mVShaper = vShaper;
+ mVOperation = vOperation;
+ }
+
+ @Override
+ public String eventToString() {
+ return "Fade Event:" + " player piid:" + mPlayerIId
+ + " uid/pid:" + mClientUid + "/" + mClientPid
+ + " player type:"
+ + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ + " attr:" + mPlayerAttr
+ + " volume shaper:" + mVShaper
+ + " volume operation:" + mVOperation;
+ }
+ }
+
private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
private final boolean mSkipRamp;
diff --git a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
index f1d42f3571a9..4a29eca5eef7 100644
--- a/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
+++ b/services/core/java/com/android/server/audio/PlayerFocusEnforcer.java
@@ -79,4 +79,11 @@ public interface PlayerFocusEnforcer {
* @return delay in milliseconds
*/
long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa);
+
+ /**
+ * Check if the fade should be enforced
+ *
+ * @return {@code true} if fade should be enforced, {@code false} otherwise
+ */
+ boolean shouldEnforceFade();
} \ No newline at end of file
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 6af223b3660a..0f964bb75944 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
@@ -78,6 +78,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -99,6 +100,9 @@ public class FaceService extends SystemService {
mBiometricStateCallback;
@NonNull
private final FaceProviderFunction mFaceProviderFunction;
+ @NonNull private final Function<String, FaceProvider> mFaceProvider;
+ @NonNull
+ private final Supplier<String[]> mAidlInstanceNameSupplier;
interface FaceProviderFunction {
FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
@@ -671,23 +675,9 @@ public class FaceService extends SystemService {
final List<ServiceProvider> providers = new ArrayList<>();
for (String instance : instances) {
- final String fqName = IFace.DESCRIPTOR + "/" + instance;
- final IFace face = IFace.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
- if (face == null) {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
- continue;
- }
- try {
- final SensorProps[] props = face.getSensorProps();
- final FaceProvider provider = new FaceProvider(getContext(),
- mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
- false /* resetLockoutRequiresChallenge */);
- providers.add(provider);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
- }
+ final FaceProvider provider = mFaceProvider.apply(instance);
+ Slog.i(TAG, "Adding AIDL provider: " + instance);
+ providers.add(provider);
}
return providers;
@@ -700,7 +690,7 @@ public class FaceService extends SystemService {
mRegistry.registerAll(() -> {
List<String> aidlSensors = new ArrayList<>();
- final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
@@ -813,11 +803,15 @@ public class FaceService extends SystemService {
public FaceService(Context context) {
this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
+ () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
}
- @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
- Supplier<IBiometricService> biometricServiceSupplier) {
+ @VisibleForTesting FaceService(Context context,
+ FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier,
+ Function<String, FaceProvider> faceProvider,
+ Supplier<String[]> aidlInstanceNameSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
@@ -830,6 +824,28 @@ public class FaceService extends SystemService {
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+ mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+
+ mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
+ final String fqName = IFace.DESCRIPTOR + "/" + name;
+ final IFace face = IFace.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ if (face == null) {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ return null;
+ }
+ try {
+ final SensorProps[] props = face.getSensorProps();
+ return new FaceProvider(getContext(),
+ mBiometricStateCallback, props, name, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+ }
+
+ return null;
+ };
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 2314bb772494..8910b6e58432 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -72,12 +74,15 @@ public class AutomaticBrightnessController {
@IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = {
AUTO_BRIGHTNESS_MODE_DEFAULT,
AUTO_BRIGHTNESS_MODE_IDLE,
+ AUTO_BRIGHTNESS_MODE_DOZE
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutomaticBrightnessMode{}
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
+ public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+ public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
@@ -616,12 +621,13 @@ public class AutomaticBrightnessController {
pw.println(" mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
pw.println(" mForegroundAppCategory=" + mForegroundAppCategory);
pw.println(" mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
- pw.println(" Current mode=" + mCurrentBrightnessMapper.getMode());
+ pw.println(" Current mode="
+ + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode()));
pw.println();
for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
- pw.println(" Mapper for mode " + modeToString(mBrightnessMappingStrategyMap.keyAt(i))
- + "=");
+ pw.println(" Mapper for mode "
+ + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "=");
mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
mBrightnessRangeController.getNormalBrightnessMax());
}
@@ -1224,14 +1230,6 @@ public class AutomaticBrightnessController {
}
}
- private String modeToString(@AutomaticBrightnessMode int mode) {
- return switch (mode) {
- case AUTO_BRIGHTNESS_MODE_DEFAULT -> "default";
- case AUTO_BRIGHTNESS_MODE_IDLE -> "idle";
- default -> Integer.toString(mode);
- };
- }
-
private class ShortTermModel {
// When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
// user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index f2ffd4d3f1d9..8405e0a52084 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -19,15 +19,18 @@ package com.android.server.display;
import static android.text.TextUtils.formatSimple;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
import android.annotation.Nullable;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessCorrection;
import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.LongArray;
import android.util.MathUtils;
import android.util.Pair;
@@ -79,41 +82,50 @@ public abstract class BrightnessMappingStrategy {
* Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
* mode.
*
- * @param resources
+ * @param context
* @param displayDeviceConfig
* @param mode The auto-brightness mode. Different modes use different brightness curves
* @param displayWhiteBalanceController
* @return the BrightnessMappingStrategy
*/
@Nullable
- static BrightnessMappingStrategy create(Resources resources,
+ static BrightnessMappingStrategy create(Context context,
DisplayDeviceConfig displayDeviceConfig,
@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
+ @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
// Display independent, mode dependent values
float[] brightnessLevelsNits = null;
float[] brightnessLevels = null;
float[] luxLevels = null;
+ int preset = Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
switch (mode) {
case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
- luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
- brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ brightnessLevels =
+ displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
}
case AUTO_BRIGHTNESS_MODE_IDLE -> {
- brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+ brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray(
com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
- luxLevels = getLuxLevels(resources.getIntArray(
+ luxLevels = getLuxLevels(context.getResources().getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
}
+ case AUTO_BRIGHTNESS_MODE_DOZE -> {
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ brightnessLevels =
+ displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
}
// Display independent, mode independent values
- float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
+ float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction(
com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
1, 1);
- long shortTermModelTimeout = resources.getInteger(
+ long shortTermModelTimeout = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
// Display dependent values - used for physical mapping strategy nits -> brightness
@@ -818,6 +830,8 @@ public abstract class BrightnessMappingStrategy {
private float mAutoBrightnessAdjustment;
private float mUserLux;
private float mUserBrightness;
+
+ @Nullable
private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
@AutomaticBrightnessController.AutomaticBrightnessMode
@@ -833,7 +847,7 @@ public abstract class BrightnessMappingStrategy {
public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
float[] brightness, float maxGamma,
@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
- DisplayWhiteBalanceController displayWhiteBalanceController) {
+ @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
"Nits and brightness arrays must not be empty!");
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7d22a87065b4..bd22e1d21dea 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1591,25 +1591,18 @@ public class DisplayDeviceConfig {
}
/**
- * @return The default auto-brightness brightening ambient lux levels
- */
- public float[] getAutoBrightnessBrighteningLevelsLux() {
- if (mDisplayBrightnessMapping == null) {
- return null;
- }
- return mDisplayBrightnessMapping.getLuxArray();
- }
-
- /**
* @param mode The auto-brightness mode
- * @param setting The brightness setting
- * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening ambient lux levels for the specified mode
+ * and preset
*/
- public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String setting) {
+ public float[] getAutoBrightnessBrighteningLevelsLux(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
if (mDisplayBrightnessMapping == null) {
return null;
}
- return mDisplayBrightnessMapping.getLuxArray(mode, setting);
+ return mDisplayBrightnessMapping.getLuxArray(mode, preset);
}
/**
@@ -1623,25 +1616,17 @@ public class DisplayDeviceConfig {
}
/**
- * @return The default auto-brightness brightening levels
- */
- public float[] getAutoBrightnessBrighteningLevels() {
- if (mDisplayBrightnessMapping == null) {
- return null;
- }
- return mDisplayBrightnessMapping.getBrightnessArray();
- }
-
- /**
* @param mode The auto-brightness mode
- * @param setting The brightness setting
- * @return Auto brightness brightening backlight levels for the specified mode and setting
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening levels for the specified mode and preset
*/
- public float[] getAutoBrightnessBrighteningLevels(String mode, String setting) {
+ public float[] getAutoBrightnessBrighteningLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
if (mDisplayBrightnessMapping == null) {
return null;
}
- return mDisplayBrightnessMapping.getBrightnessArray(mode, setting);
+ return mDisplayBrightnessMapping.getBrightnessArray(mode, preset);
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08ff2a1a..bc3f9dd3cb8c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@ public final class DisplayManagerService extends SystemService {
if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerControllerInterface displayPowerController =
mDisplayPowerControllers.get(id);
- ready &= displayPowerController.requestPowerState(request,
- waitForNegativeProximity);
+ if (displayPowerController != null) {
+ ready &= displayPowerController.requestPowerState(request,
+ waitForNegativeProximity);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9c19b4..a43f93a9beac 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -65,6 +67,22 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display
return true;
}
+ @Override
+ public float[] getAutoBrightnessLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLevels(mode);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2685efecd6a1..734381b1ddb0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -735,7 +735,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mCdsi = null;
}
- setUpAutoBrightness(resources, handler);
+ setUpAutoBrightness(context, handler);
mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
&& !resources.getBoolean(
@@ -1075,7 +1075,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
loadBrightnessRampRates();
loadProximitySensor();
loadNitsRange(mContext.getResources());
- setUpAutoBrightness(mContext.getResources(), mHandler);
+ setUpAutoBrightness(mContext, mHandler);
reloadReduceBrightColours();
setAnimatorRampSpeeds(/* isIdleMode= */ false);
mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
@@ -1145,7 +1145,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
handleBrightnessModeChange();
}
- private void setUpAutoBrightness(Resources resources, Handler handler) {
+ private void setUpAutoBrightness(Context context, Handler handler) {
mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
if (!mUseSoftwareAutoBrightnessConfig) {
@@ -1155,21 +1155,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
BrightnessMappingStrategy defaultModeBrightnessMapper =
- mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+ mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
mDisplayWhiteBalanceController);
brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
defaultModeBrightnessMapper);
- final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
if (isIdleScreenBrightnessEnabled) {
BrightnessMappingStrategy idleModeBrightnessMapper =
- BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
- AUTO_BRIGHTNESS_MODE_IDLE,
- mDisplayWhiteBalanceController);
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
if (idleModeBrightnessMapper != null) {
- brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
- idleModeBrightnessMapper);
+ brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
}
}
@@ -1181,7 +1179,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = resources.getFraction(
+ final float dozeScaleFactor = context.getResources().getFraction(
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
@@ -1265,14 +1263,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
.getAutoBrightnessBrighteningLightDebounceIdle();
long darkeningLightDebounceIdle = mDisplayDeviceConfig
.getAutoBrightnessDarkeningLightDebounceIdle();
- boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = resources.getInteger(
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
com.android.internal.R.integer.config_lightSensorWarmupTime);
- int lightSensorRate = resources.getInteger(
+ int lightSensorRate = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = resources.getInteger(
+ int initialLightSensorRate = context.getResources().getInteger(
com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
@@ -2216,6 +2214,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
@@ -3645,12 +3657,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
userNits);
}
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
- return BrightnessMappingStrategy.create(resources,
- displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
- displayWhiteBalanceController);
+ return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 6d09cc9d37ba..7df61142475a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -17,7 +17,9 @@
package com.android.server.display;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -623,7 +625,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mCdsi = null;
}
- setUpAutoBrightness(resources, handler);
+ setUpAutoBrightness(context, handler);
mColorFadeEnabled = mInjector.isColorFadeEnabled()
&& !resources.getBoolean(
@@ -904,7 +906,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// updated here.
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
- setUpAutoBrightness(mContext.getResources(), mHandler);
+ setUpAutoBrightness(mContext, mHandler);
reloadReduceBrightColours();
setAnimatorRampSpeeds(/* isIdleMode= */ false);
@@ -975,10 +977,15 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ if (mFlags.areAutoBrightnessModesEnabled()) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
+ /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+ }
handleBrightnessModeChange();
}
- private void setUpAutoBrightness(Resources resources, Handler handler) {
+ private void setUpAutoBrightness(Context context, Handler handler) {
mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
if (!mUseSoftwareAutoBrightnessConfig) {
@@ -988,16 +995,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
BrightnessMappingStrategy defaultModeBrightnessMapper =
- mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+ mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
mDisplayWhiteBalanceController);
brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
defaultModeBrightnessMapper);
- final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+ final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
R.bool.config_enableIdleScreenBrightnessMode);
if (isIdleScreenBrightnessEnabled) {
BrightnessMappingStrategy idleModeBrightnessMapper =
- BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
AUTO_BRIGHTNESS_MODE_IDLE,
mDisplayWhiteBalanceController);
if (idleModeBrightnessMapper != null) {
@@ -1006,6 +1013,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
}
+ BrightnessMappingStrategy dozeModeBrightnessMapper =
+ BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
+ if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
+ brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
+ }
+
float userLux = BrightnessMappingStrategy.INVALID_LUX;
float userNits = BrightnessMappingStrategy.INVALID_NITS;
if (mAutomaticBrightnessController != null) {
@@ -1014,7 +1028,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = resources.getFraction(
+ final float dozeScaleFactor = context.getResources().getFraction(
R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
@@ -1098,14 +1112,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
.getAutoBrightnessBrighteningLightDebounceIdle();
long darkeningLightDebounceIdle = mDisplayDeviceConfig
.getAutoBrightnessDarkeningLightDebounceIdle();
- boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
- int lightSensorWarmUpTimeConfig = resources.getInteger(
+ int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
R.integer.config_lightSensorWarmupTime);
- int lightSensorRate = resources.getInteger(
+ int lightSensorRate = context.getResources().getInteger(
R.integer.config_autoBrightnessLightSensorRate);
- int initialLightSensorRate = resources.getInteger(
+ int initialLightSensorRate = context.getResources().getInteger(
R.integer.config_autoBrightnessInitialLightSensorRate);
if (initialLightSensorRate == -1) {
initialLightSensorRate = lightSensorRate;
@@ -1349,6 +1363,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
+ // Switch to doze auto-brightness mode if needed
+ if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+ && !mAutomaticBrightnessController.isInIdleMode()) {
+ setAutomaticScreenBrightnessMode(Display.isDozeState(state)
+ ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+ }
+
final boolean userSetBrightnessChanged = mDisplayBrightnessController
.updateUserSetScreenBrightness();
@@ -1865,6 +1886,24 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
@@ -2984,6 +3023,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
handleBrightnessModeChange();
+ } else if (uri.equals(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+ UserHandle.USER_CURRENT);
+ Slog.i(mTag, "Setting up auto-brightness for preset "
+ + autoBrightnessPresetToString(preset));
+ setUpAutoBrightness(mContext, mHandler);
+ sendUpdatePowerState();
} else {
handleSettingsChange(false /* userSwitch */);
}
@@ -3102,12 +3151,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
userNits);
}
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
- return BrightnessMappingStrategy.create(resources,
- displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
- displayWhiteBalanceController);
+ return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c27918430254..13acb3f79c67 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@ public interface DisplayPowerControllerInterface {
* Indicate that boot has been completed and the screen is ready to update.
*/
void onBootCompleted();
+
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index f994c0556c82..bcf27b4e8f0a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,9 +26,12 @@ import android.util.Slog;
import android.view.Choreographer;
import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
/**
* Controls the display power state.
@@ -75,10 +78,19 @@ final class DisplayPowerState {
private Runnable mCleanListener;
+ private Executor mAsyncDestroyExecutor;
+
private volatile boolean mStopped;
DisplayPowerState(
DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
+ this(blanker, colorFade, displayId, displayState, BackgroundThread.getExecutor());
+ }
+
+ @VisibleForTesting
+ DisplayPowerState(
+ DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
+ Executor asyncDestroyExecutor) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
@@ -86,6 +98,7 @@ final class DisplayPowerState {
mPhotonicModulator = new PhotonicModulator();
mPhotonicModulator.start();
mDisplayId = displayId;
+ mAsyncDestroyExecutor = asyncDestroyExecutor;
// At boot time, we don't know the screen's brightness,
// so prepare to set it to a known state when the state is next applied.
@@ -321,7 +334,7 @@ final class DisplayPowerState {
mStopped = true;
mPhotonicModulator.interrupt();
if (mColorFade != null) {
- mColorFade.destroy();
+ mAsyncDestroyExecutor.execute(mColorFade::destroy);
}
mCleanListener = null;
mHandler.removeCallbacksAndMessages(null);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 200d88a78dd7..01a8d360a526 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -104,8 +104,7 @@ public class HdrClamper {
public void resetHdrConfig(HdrBrightnessData data, int width, int height,
float minimumHdrPercentOfScreen, IBinder displayToken) {
mHdrBrightnessData = data;
- mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
- : (float) (width * height) * minimumHdrPercentOfScreen;
+ mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
mHdrListener.unregister(mRegisteredDisplayToken);
@@ -115,7 +114,7 @@ public class HdrClamper {
// new token not null and hdr min % of the screen is set, subscribe.
// e.g. for virtual display, HBM data will be missing and HdrListener
// should not be registered
- if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
+ if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
mHdrListener.register(displayToken);
mRegisteredDisplayToken = displayToken;
}
@@ -140,8 +139,11 @@ public class HdrClamper {
pw.println(" mDesiredMaxBrightness=" + mDesiredMaxBrightness);
pw.println(" mTransitionRate=" + mTransitionRate);
pw.println(" mDesiredTransitionRate=" + mDesiredTransitionRate);
+ pw.println(" mHdrVisible=" + mHdrVisible);
+ pw.println(" mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
pw.println(" mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
: mHdrBrightnessData.toString()));
+ pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
pw.println(" mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 21628503adcf..6978686faa12 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -16,11 +16,17 @@
package com.android.server.display.config;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
import android.content.Context;
import android.os.PowerManager;
+import android.provider.Settings;
import android.util.Spline;
import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -33,7 +39,9 @@ import java.util.Map;
*/
public class DisplayBrightnessMappingConfig {
- private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = "default_normal";
+ private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY =
+ AutoBrightnessModeName._default.getRawName() + "_"
+ + AutoBrightnessSettingName.normal.getRawName();
/**
* Array of desired screen brightness in nits corresponding to the lux values
@@ -45,19 +53,22 @@ public class DisplayBrightnessMappingConfig {
/**
* Map of arrays of desired screen brightness corresponding to the lux values
- * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness setting.
+ * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness preset.
* The brightness values must be non-negative and non-decreasing. They must be between
* {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
*
- * The keys are a concatenation of the auto-brightness mode and the brightness setting
- * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
- * doze_dim, doze_bright.
+ * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+ * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+ * doze_bright.
+ *
+ * The presets are used on devices that allow users to choose from a set of predefined options
+ * in display auto-brightness settings.
*/
private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>();
/**
* Map of arrays of light sensor lux values to define our levels for auto-brightness support,
- * indexed by the auto-brightness mode and the brightness setting.
+ * indexed by the auto-brightness mode and the brightness preset.
*
* The first lux value in every array is always 0.
*
@@ -69,9 +80,12 @@ public class DisplayBrightnessMappingConfig {
* Spline interpolation is used to determine the auto-brightness values for lux levels between
* these control points.
*
- * The keys are a concatenation of the auto-brightness mode and the brightness setting
- * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
- * doze_dim, doze_bright.
+ * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+ * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+ * doze_bright.
+ *
+ * The presets are used on devices that allow users to choose from a set of predefined options
+ * in display auto-brightness settings.
*/
private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>();
@@ -138,19 +152,16 @@ public class DisplayBrightnessMappingConfig {
}
/**
- * @return The default auto-brightness brightening ambient lux levels
- */
- public float[] getLuxArray() {
- return mBrightnessLevelsLuxMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
- }
-
- /**
* @param mode The auto-brightness mode
- * @param setting The brightness setting
- * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening ambient lux levels for the specified mode
+ * and preset
*/
- public float[] getLuxArray(String mode, String setting) {
- return mBrightnessLevelsLuxMap.get(mode + "_" + setting);
+ public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+ int preset) {
+ return mBrightnessLevelsLuxMap.get(
+ autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
}
/**
@@ -161,19 +172,15 @@ public class DisplayBrightnessMappingConfig {
}
/**
- * @return The default auto-brightness brightening levels
- */
- public float[] getBrightnessArray() {
- return mBrightnessLevelsMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
- }
-
- /**
* @param mode The auto-brightness mode
- * @param setting The brightness setting
- * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The default auto-brightness brightening levels for the specified mode and preset
*/
- public float[] getBrightnessArray(String mode, String setting) {
- return mBrightnessLevelsMap.get(mode + "_" + setting);
+ public float[] getBrightnessArray(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+ return mBrightnessLevelsMap.get(
+ autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
}
@Override
@@ -205,6 +212,44 @@ public class DisplayBrightnessMappingConfig {
+ ", mBrightnessLevelsMap= " + brightnessLevelsMapString;
}
+ /**
+ * @param mode The auto-brightness mode
+ * @return The string representing the mode
+ */
+ public static String autoBrightnessModeToString(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ switch (mode) {
+ case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
+ return AutoBrightnessModeName._default.getRawName();
+ }
+ case AUTO_BRIGHTNESS_MODE_IDLE -> {
+ return AutoBrightnessModeName.idle.getRawName();
+ }
+ case AUTO_BRIGHTNESS_MODE_DOZE -> {
+ return AutoBrightnessModeName.doze.getRawName();
+ }
+ default -> throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ }
+
+ /**
+ * @param preset The brightness preset. Presets are used on devices that allow users to choose
+ * from a set of predefined options in display auto-brightness settings.
+ * @return The string representing the preset
+ */
+ public static String autoBrightnessPresetToString(int preset) {
+ return switch (preset) {
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM ->
+ AutoBrightnessSettingName.dim.getRawName();
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL ->
+ AutoBrightnessSettingName.normal.getRawName();
+ case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT ->
+ AutoBrightnessSettingName.bright.getRawName();
+ default -> throw new IllegalArgumentException(
+ "Unknown auto-brightness preset value: " + preset);
+ };
+ }
+
private float[] brightnessArrayIntToFloat(int[] brightnessInt,
Spline backlightToBrightnessSpline) {
float[] brightnessFloat = new float[brightnessInt.length];
diff --git a/services/core/java/com/android/server/flags/OWNERS b/services/core/java/com/android/server/flags/OWNERS
index 535a7509601c..60ceb128e194 100644
--- a/services/core/java/com/android/server/flags/OWNERS
+++ b/services/core/java/com/android/server/flags/OWNERS
@@ -1 +1,2 @@
-per-file pinner.aconfig = edgararriaga@google.com \ No newline at end of file
+per-file pinner.aconfig = edgararriaga@google.com
+per-file compaction.aconfig = edgararriaga@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/compaction.aconfig b/services/core/java/com/android/server/flags/compaction.aconfig
new file mode 100644
index 000000000000..58cc56029ea1
--- /dev/null
+++ b/services/core/java/com/android/server/flags/compaction.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+ name: "disable_system_compaction"
+ namespace: "system_performance"
+ description: "This flag controls if all processes compaction should happen during idle maintenance."
+ bug: "314328789"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 64abb81d0e7a..81204ef5d7ed 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1314,7 +1314,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
*/
protected void disableDevice(
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
- removeAction(AbsoluteVolumeAudioStatusAction.class);
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
removeAction(ActiveSourceAction.class);
removeAction(ResendCecCommandAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 29303aab6fa9..6157402279a9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -308,7 +308,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
removeAction(OneTouchPlayAction.class);
removeAction(DevicePowerStatusAction.class);
- removeAction(AbsoluteVolumeAudioStatusAction.class);
super.disableDevice(initiatedByCec, callback);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5831b29437dc..1cd267dee2fe 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1336,7 +1336,6 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
removeAction(OneTouchRecordAction.class);
removeAction(TimerRecordingAction.class);
removeAction(NewDeviceAction.class);
- removeAction(AbsoluteVolumeAudioStatusAction.class);
// Remove pending actions.
removeAction(RequestActiveSourceAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 92537064f766..eaf754dc7520 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3793,6 +3793,11 @@ public class HdmiControlService extends SystemService {
}
}
});
+
+ // Make sure we switch away from absolute volume behavior (AVB) when entering standby.
+ // We do this because AVB should not be used unless AbsoluteVolumeAudioStatusAction exists,
+ // and the action cannot exist in standby because there are no local devices.
+ checkAndUpdateAbsoluteVolumeBehavior();
}
boolean canGoToStandby() {
@@ -4446,10 +4451,11 @@ public class HdmiControlService extends SystemService {
* This allows the volume level of the System Audio device to be tracked and set by Android.
*
* Absolute volume behavior requires the following conditions:
- * 1. If the System Audio Device is an Audio System: System Audio Mode is active
- * 2. All AVB-capable audio output devices are already using full/absolute volume behavior
- * 3. CEC volume is enabled
- * 4. The System Audio device supports the <Set Audio Volume Level> message
+ * 1. The device is not in standby or transient to standby
+ * 2. If the System Audio Device is an Audio System: System Audio Mode is active
+ * 3. All AVB-capable audio output devices are already using full/absolute volume behavior
+ * 4. CEC volume is enabled
+ * 5. The System Audio device supports the <Set Audio Volume Level> message
*
* This method enables adjust-only absolute volume behavior on TV panels when conditions
* 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
@@ -4465,10 +4471,16 @@ public class HdmiControlService extends SystemService {
return;
}
+ // Condition 1: The device is not in standby or transient to standby
+ if (mPowerStatusController != null && isPowerStandbyOrTransient()) {
+ switchToFullVolumeBehavior();
+ return;
+ }
+
HdmiCecLocalDevice localCecDevice;
if (isTvDevice() && tv() != null) {
localCecDevice = tv();
- // Condition 1: TVs need System Audio Mode to be active
+ // Condition 2: TVs need System Audio Mode to be active
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
// TV is the System Audio Device instead.)
if (!isSystemAudioActivated()) {
@@ -4485,7 +4497,7 @@ public class HdmiControlService extends SystemService {
HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
localCecDevice.findAudioReceiverAddress());
- // Condition 2: All AVB-capable audio outputs already use full/absolute volume behavior
+ // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior
// We only need to check the first AVB-capable audio output because only TV panels
// have more than one of them, and they always have the same volume behavior.
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
@@ -4493,7 +4505,7 @@ public class HdmiControlService extends SystemService {
boolean alreadyUsingFullOrAbsoluteVolume =
FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior);
- // Condition 3: CEC volume is enabled
+ // Condition 4: CEC volume is enabled
boolean cecVolumeEnabled =
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
@@ -4509,7 +4521,7 @@ public class HdmiControlService extends SystemService {
return;
}
- // Condition 4: The System Audio device supports <Set Audio Volume Level>
+ // Condition 5: The System Audio device supports <Set Audio Volume Level>
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
@@ -4556,6 +4568,8 @@ public class HdmiControlService extends SystemService {
* are currently used. Removes the action for handling volume updates for these behaviors.
*/
private void switchToFullVolumeBehavior() {
+ Slog.d(TAG, "Switching to full volume behavior");
+
if (playback() != null) {
playback().removeAvbAudioStatusAction();
} else if (tv() != null) {
@@ -4597,12 +4611,14 @@ public class HdmiControlService extends SystemService {
// Otherwise, enable adjust-only AVB on TVs only.
if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
== DeviceFeatures.FEATURE_SUPPORTED) {
+ Slog.d(TAG, "Enabling absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
device, volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
}
} else if (tv() != null) {
+ Slog.d(TAG, "Enabling adjust-only absolute volume behavior");
for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
device, volumeInfo, mServiceThreadExecutor,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 773293f83323..a88d85e07100 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -229,31 +229,6 @@ final class InputMethodUtils {
}
}
- private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
- String enabledInputMethodsStr,
- TextUtils.SimpleStringSplitter inputMethodSplitter,
- TextUtils.SimpleStringSplitter subtypeSplitter) {
- ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- inputMethodSplitter.setString(enabledInputMethodsStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<>();
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeHashes.add(subtypeSplitter.next());
- }
- imsList.add(new Pair<>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
boolean copyOnWrite) {
mMethodMap = methodMap;
@@ -351,18 +326,30 @@ final class InputMethodUtils {
}
List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
- }
-
- List<String> getEnabledInputMethodNames() {
- List<String> result = new ArrayList<>();
- for (Pair<String, ArrayList<String>> pair :
- getEnabledInputMethodsAndSubtypeListLocked()) {
- result.add(pair.first);
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
}
- return result;
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<>();
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeHashes.add(subtypeSplitter.next());
+ }
+ imsList.add(new Pair<>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
}
void appendAndPutEnabledInputMethodLocked(String id) {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index f2dcba45e312..5514ec701c15 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -59,6 +59,7 @@ import android.util.apk.SourceStampVerifier;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
@@ -67,7 +68,7 @@ import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.RuleMetadata;
import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -141,7 +142,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
return new AppIntegrityManagerServiceImpl(
context,
LocalServices.getService(PackageManagerInternal.class),
- PackageParser2::forParsingFileWithDefaults,
+ PackageParserUtils::forParsingFileWithDefaults,
RuleEvaluationEngine.getRuleEvaluationEngine(),
IntegrityFileManager.getInstance(),
handlerThread.getThreadHandler());
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5424b8..9c4225dc2542 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
location.setExtras(mLocationExtras.getBundle());
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
if (mStarted) {
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
}
}
- reportLocation(LocationResult.wrap(locations).validate());
+ try {
+ reportLocation(LocationResult.wrap(locations).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 78c8cdee7845..403b421639cb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -19,6 +19,7 @@ package com.android.server.location.gnss;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import android.content.Context;
+import android.location.flags.Flags;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -48,6 +49,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Handles network connection requests and network state change updates for AGPS data download.
@@ -91,6 +93,10 @@ class GnssNetworkConnectivityHandler {
// network with SUPL connectivity or report an error.
private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 20 * 1000;
+ // If the chipset does not request to release a SUPL connection before the specified timeout in
+ // milliseconds, the connection will be automatically released.
+ private static final long SUPL_CONNECTION_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
+
private static final int HASH_MAP_INITIAL_CAPACITY_TO_TRACK_CONNECTED_NETWORKS = 5;
// Keeps track of networks and their state as notified by the network request callbacks.
@@ -121,6 +127,8 @@ class GnssNetworkConnectivityHandler {
private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
private final PowerManager.WakeLock mWakeLock;
+ private final Object mSuplConnectionReleaseOnTimeoutToken = new Object();
+
/**
* Network attributes needed when updating HAL about network connectivity status changes.
*/
@@ -609,6 +617,13 @@ class GnssNetworkConnectivityHandler {
mSuplConnectivityCallback,
mHandler,
SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS);
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Schedule to release the SUPL connection after timeout
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ mHandler.postDelayed(() -> handleReleaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN),
+ mSuplConnectionReleaseOnTimeoutToken,
+ SUPL_CONNECTION_TIMEOUT_MILLIS);
+ }
} catch (RuntimeException e) {
Log.e(TAG, "Failed to request network.", e);
mSuplConnectivityCallback = null;
@@ -639,6 +654,10 @@ class GnssNetworkConnectivityHandler {
Log.d(TAG, message);
}
+ if (Flags.releaseSuplConnectionOnTimeout()) {
+ // Remove pending task to avoid releasing an incorrect connection
+ mHandler.removeCallbacksAndMessages(mSuplConnectionReleaseOnTimeoutToken);
+ }
if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) {
return;
}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80a5bbb..7d44aecf5607 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@ import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@ public class LocationProviderManager extends
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
if (D) {
Log.v(TAG, mName + " provider registration " + getIdentity()
- + " dropped delivery - too fast");
+ + " dropped delivery - too fast (deltaMs="
+ + deltaMs + ").");
}
return false;
}
@@ -2574,29 +2576,17 @@ public class LocationProviderManager extends
@GuardedBy("mMultiplexerLock")
@Nullable
private LocationResult processReportedLocation(LocationResult locationResult) {
- LocationResult processed = locationResult.filter(location -> {
- if (!location.isMock()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
- return false;
- }
- }
-
- if (!location.isComplete()) {
- Log.e(TAG, "blocking incomplete location from " + mName + " provider");
- return false;
- }
-
- return true;
- });
- if (processed == null) {
+ try {
+ locationResult.validate();
+ } catch (BadLocationException e) {
+ Log.e(TAG, "Dropping invalid locations: " + e);
return null;
}
// Attempt to add a missing MSL altitude on behalf of the provider.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
"enable_location_provider_manager_msl", true)) {
- return processed.map(location -> {
+ return locationResult.map(location -> {
if (!location.hasMslAltitude() && location.hasAltitude()) {
try {
Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@ public class LocationProviderManager extends
return location;
});
}
- return processed;
+ return locationResult;
}
@GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4bab14..4efacd7a2995 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@ public class MockLocationProvider extends AbstractLocationProvider {
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da28217..a597edd93515 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider implements
return;
}
- reportLocation(LocationResult.wrap(location).validate());
+ reportLocation(LocationResult.wrap(location));
}
}
@@ -316,8 +316,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider implements
if (mProxy != this) {
return;
}
-
- reportLocation(LocationResult.wrap(locations).validate());
+ reportLocation(LocationResult.wrap(locations));
}
}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 850449595d74..0eb9166371dc 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -87,6 +87,8 @@ import java.util.Objects;
@NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
+ @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
@NonNull
private final AudioManager.OnDevicesForAttributesChangedListener
mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
@@ -113,6 +115,10 @@ import java.util.Objects;
mHandler = new Handler(Objects.requireNonNull(looper));
mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+
+ mBuiltInSpeakerSuitabilityStatus =
+ DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
mBluetoothRouteController =
new AudioPoliciesBluetoothRouteController(
mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
@@ -373,14 +379,19 @@ import java.util.Objects;
// from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
routeId = systemRouteInfo.mDefaultRouteId;
}
- return new MediaRoute2Info.Builder(routeId, humanReadableName)
+ MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName)
.setType(systemRouteInfo.mMediaRoute2InfoType)
.setAddress(address)
.setSystemRoute(true)
.addFeature(FEATURE_LIVE_AUDIO)
.addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+ if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) {
+ builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+ }
+
+ return builder.build();
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 9f175a9a0277..8b62cc974862 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -81,6 +81,30 @@ import java.util.List;
}
}
+ /** Returns device route availability status. */
+ @MediaRoute2Info.SuitabilityStatus
+ static int getBuiltInSpeakerSuitabilityStatus(@NonNull Context context) {
+ if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ // Route is always suitable if the flag is disabled.
+ return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+ }
+
+ int availabilityStatus =
+ context.getResources()
+ .getInteger(
+ com.android.internal.R.integer
+ .config_mediaRouter_builtInSpeakerSuitability);
+
+ switch (availabilityStatus) {
+ case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER:
+ case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER:
+ case MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER:
+ return availabilityStatus;
+ default:
+ return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+ }
+ }
+
/** Returns the currently selected device (built-in or wired) route. */
@NonNull
MediaRoute2Info getSelectedRoute();
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index c0f28346705c..65b0ad0d61a0 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -72,6 +72,8 @@ import java.util.Objects;
@NonNull
private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+ @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
private int mDeviceVolume;
private MediaRoute2Info mDeviceRoute;
@@ -90,6 +92,9 @@ import java.util.Objects;
mAudioManager = audioManager;
mAudioService = audioService;
+ mBuiltInSpeakerSuitabilityStatus =
+ DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
AudioRoutesInfo newAudioRoutes = null;
try {
newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
@@ -165,19 +170,28 @@ import java.util.Objects;
}
synchronized (this) {
- return new MediaRoute2Info.Builder(
- DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
- .setVolumeHandling(mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolume(mDeviceVolume)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setType(type)
- .addFeature(FEATURE_LIVE_AUDIO)
- .addFeature(FEATURE_LIVE_VIDEO)
- .addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
+ MediaRoute2Info.Builder builder =
+ new MediaRoute2Info.Builder(
+ DEVICE_ROUTE_ID,
+ mContext.getResources().getText(name).toString())
+ .setVolumeHandling(
+ mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
+ .setVolumeMax(
+ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setType(type)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LIVE_VIDEO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+ if (type == TYPE_BUILTIN_SPEAKER) {
+ builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+ }
+
+ return builder.build();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 8149847d70c0..1bc2a5eb1351 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -24,6 +24,7 @@ import android.media.MediaRoute2ProviderInfo;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
+import android.os.UserHandle;
import com.android.internal.annotations.GuardedBy;
@@ -54,8 +55,15 @@ abstract class MediaRoute2Provider {
mCallback = callback;
}
- public abstract void requestCreateSession(long requestId, String packageName, String routeId,
- @Nullable Bundle sessionHints);
+ public abstract void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ @Nullable Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName);
+
public abstract void releaseSession(long requestId, String sessionId);
public abstract void updateDiscoveryPreference(
@@ -63,7 +71,14 @@ abstract class MediaRoute2Provider {
public abstract void selectRoute(long requestId, String sessionId, String routeId);
public abstract void deselectRoute(long requestId, String sessionId, String routeId);
- public abstract void transferToRoute(long requestId, String sessionId, String routeId);
+
+ public abstract void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason);
public abstract void setRouteVolume(long requestId, String routeId, int volume);
public abstract void setSessionVolume(long requestId, String sessionId, int volume);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 330818ed17ca..ae889d8255c6 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -98,8 +98,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
}
@Override
- public void requestCreateSession(long requestId, String packageName, String routeId,
- Bundle sessionHints) {
+ public void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
if (mConnectionReady) {
mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
updateBinding();
@@ -141,7 +147,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
}
@Override
- public void transferToRoute(long requestId, String sessionId, String routeId) {
+ public void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (mConnectionReady) {
mActiveConnection.transferToRoute(requestId, sessionId, routeId);
}
@@ -649,6 +661,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
+ "Disallowed route: "
+ route);
}
+
+ if (route.getSuitabilityStatus()
+ == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) {
+ throw new SecurityException(
+ "Only the system is allowed to set not suitable for transfer status. "
+ + "Disallowed route: "
+ + route);
+ }
}
Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e18727459c6..38f0df41db04 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -337,18 +337,47 @@ class MediaRouter2ServiceImpl {
}
}
- public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId,
- long managerRequestId, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, Bundle sessionHints) {
+ public void requestCreateSessionWithRouter2(
+ @NonNull IMediaRouter2 router,
+ int requestId,
+ long managerRequestId,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ Bundle sessionHints,
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
+ synchronized (mLock) {
+ if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE
+ || transferInitiatorUserHandle == null
+ || transferInitiatorPackageName == null) {
+ final IBinder binder = router.asBinder();
+ final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+ transferInitiatorUserHandle = Binder.getCallingUserHandle();
+ if (routerRecord != null) {
+ transferInitiatorPackageName = routerRecord.mPackageName;
+ } else {
+ transferInitiatorPackageName = mContext.getPackageName();
+ }
+ }
+ }
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
- router, oldSession, route, sessionHints);
+ requestCreateSessionWithRouter2Locked(
+ requestId,
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ router,
+ oldSession,
+ route,
+ sessionHints);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -399,10 +428,11 @@ class MediaRouter2ServiceImpl {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
}
+ UserHandle userHandle = Binder.getCallingUserHandle();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
+ transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -588,16 +618,28 @@ class MediaRouter2ServiceImpl {
}
}
- public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager,
- int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ public void requestCreateSessionWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ int requestId,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(manager, "manager must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
+ requestCreateSessionWithManagerLocked(
+ requestId,
+ manager,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -640,18 +682,32 @@ class MediaRouter2ServiceImpl {
}
}
- public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ public void transferToRouteWithManager(
+ @NonNull IMediaRouter2Manager manager,
+ int requestId,
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
}
Objects.requireNonNull(route, "route must not be null");
+ Objects.requireNonNull(transferInitiatorUserHandle);
+ Objects.requireNonNull(transferInitiatorPackageName);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
+ transferToRouteWithManagerLocked(
+ requestId,
+ manager,
+ uniqueSessionId,
+ route,
+ RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1038,9 +1094,15 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
- private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
- @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ private void requestCreateSessionWithRouter2Locked(
+ int requestId,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ @NonNull IMediaRouter2 router,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @Nullable Bundle sessionHints) {
final IBinder binder = router.asBinder();
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -1114,9 +1176,16 @@ class MediaRouter2ServiceImpl {
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
+ obtainMessage(
+ UserHandler::requestCreateSessionWithRouter2OnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
+ uniqueRequestId,
+ managerRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ routerRecord,
+ oldSession,
+ route,
sessionHints));
}
@@ -1165,8 +1234,11 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
- private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ private void transferToRouteWithRouter2Locked(
+ @NonNull IMediaRouter2 router,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route) {
final IBinder binder = router.asBinder();
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
@@ -1191,9 +1263,16 @@ class MediaRouter2ServiceImpl {
routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
} else {
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::transferToRouteOnHandler,
+ obtainMessage(
+ UserHandler::transferToRouteOnHandler,
routerRecord.mUserRecord.mHandler,
- DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+ DUMMY_REQUEST_ID,
+ transferInitiatorUserHandle,
+ routerRecord.mPackageName,
+ routerRecord,
+ uniqueSessionId,
+ route,
+ RoutingSessionInfo.TRANSFER_REASON_APP));
}
}
@@ -1416,9 +1495,13 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
- private void requestCreateSessionWithManagerLocked(int requestId,
- @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route) {
+ private void requestCreateSessionWithManagerLocked(
+ int requestId,
+ @NonNull IMediaRouter2Manager manager,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord == null) {
return;
@@ -1464,9 +1547,16 @@ class MediaRouter2ServiceImpl {
// Before requesting to the provider, get session hints from the media router.
// As a return, media router will request to create a session.
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
+ obtainMessage(
+ UserHandler::requestRouterCreateSessionOnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, managerRecord, oldSession, route));
+ uniqueRequestId,
+ routerRecord,
+ managerRecord,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName));
}
@GuardedBy("mLock")
@@ -1521,9 +1611,14 @@ class MediaRouter2ServiceImpl {
}
@GuardedBy("mLock")
- private void transferToRouteWithManagerLocked(int requestId,
+ private void transferToRouteWithManagerLocked(
+ int requestId,
@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -1541,9 +1636,16 @@ class MediaRouter2ServiceImpl {
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::transferToRouteOnHandler,
+ obtainMessage(
+ UserHandler::transferToRouteOnHandler,
managerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, uniqueSessionId, route));
+ uniqueRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ routerRecord,
+ uniqueSessionId,
+ route,
+ transferReason));
}
@GuardedBy("mLock")
@@ -1850,6 +1952,19 @@ class MediaRouter2ServiceImpl {
}
}
+ public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
+ try {
+ mRouter.notifySessionCreated(
+ requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
+ } catch (RemoteException ex) {
+ Slog.w(
+ TAG,
+ "Failed to notify router of the session creation."
+ + " Router probably died.",
+ ex);
+ }
+ }
+
/**
* Sends the corresponding router an update for the given session.
*
@@ -1857,12 +1972,27 @@ class MediaRouter2ServiceImpl {
*/
public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
try {
- mRouter.notifySessionInfoChanged(sessionInfo);
+ mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
}
}
+ private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
+ @NonNull RoutingSessionInfo sessionInfo) {
+ UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+ String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+ if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
+ || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
+ return new RoutingSessionInfo.Builder(sessionInfo)
+ .setTransferInitiator(null, null)
+ .build();
+ }
+
+ return sessionInfo;
+ }
+
/**
* Returns a filtered copy of {@code routes} that contains only the routes that are {@link
* MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2307,9 +2437,14 @@ class MediaRouter2ServiceImpl {
return -1;
}
- private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
- @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+ private void requestRouterCreateSessionOnHandler(
+ long uniqueRequestId,
+ @NonNull RouterRecord routerRecord,
+ @NonNull ManagerRecord managerRecord,
+ @NonNull RoutingSessionInfo oldSession,
+ @NonNull MediaRoute2Info route,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
try {
if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
// The router lacks permission to modify system routing, so we hide system
@@ -2317,7 +2452,11 @@ class MediaRouter2ServiceImpl {
route = mSystemProvider.getDefaultRoute();
}
routerRecord.mRouter.requestCreateSessionByManager(
- uniqueRequestId, oldSession, route);
+ uniqueRequestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
} catch (RemoteException ex) {
Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
+ "Failed to request. Router probably died.", ex);
@@ -2326,10 +2465,15 @@ class MediaRouter2ServiceImpl {
}
}
- private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
- long managerRequestId, @NonNull RouterRecord routerRecord,
+ private void requestCreateSessionWithRouter2OnHandler(
+ long uniqueRequestId,
+ long managerRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ @NonNull RouterRecord routerRecord,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ @NonNull MediaRoute2Info route,
+ @Nullable Bundle sessionHints) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
@@ -2345,8 +2489,19 @@ class MediaRouter2ServiceImpl {
managerRequestId, oldSession, route);
mSessionCreationRequests.add(request);
- provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
- route.getOriginalId(), sessionHints);
+ int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP;
+ if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST;
+ }
+
+ provider.requestCreateSession(
+ uniqueRequestId,
+ routerRecord.mPackageName,
+ route.getOriginalId(),
+ sessionHints,
+ transferReason,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2386,9 +2541,14 @@ class MediaRouter2ServiceImpl {
}
// routerRecord can be null if the session is system's or RCN.
- private void transferToRouteOnHandler(long uniqueRequestId,
+ private void transferToRouteOnHandler(
+ long uniqueRequestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
@Nullable RouterRecord routerRecord,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId,
+ @NonNull MediaRoute2Info route,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
"transferring to")) {
return;
@@ -2399,8 +2559,13 @@ class MediaRouter2ServiceImpl {
if (provider == null) {
return;
}
- provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.transferToRoute(
+ uniqueRequestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ getOriginalId(uniqueSessionId),
+ route.getOriginalId(),
+ transferReason);
}
// routerRecord is null if and only if the session is created without the request, which
@@ -2535,6 +2700,7 @@ class MediaRouter2ServiceImpl {
// session info from them.
sessionInfo = mSystemProvider.getDefaultSessionInfo();
}
+ // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
notifySessionCreatedToRouter(
matchingRequest.mRouterRecord,
toOriginalRequestId(uniqueRequestId),
@@ -2648,12 +2814,7 @@ class MediaRouter2ServiceImpl {
private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- try {
- routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify router of the session creation."
- + " Router probably died.", ex);
- }
+ routerRecord.notifySessionCreated(requestId, sessionInfo);
}
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e562b3f0845c..7dd13142f52e 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -461,11 +461,24 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
- long managerRequestId, RoutingSessionInfo oldSession,
- MediaRoute2Info route, Bundle sessionHints) {
- mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
- oldSession, route, sessionHints);
+ public void requestCreateSessionWithRouter2(
+ IMediaRouter2 router,
+ int requestId,
+ long managerRequestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ Bundle sessionHints,
+ @Nullable UserHandle transferInitiatorUserHandle,
+ @Nullable String transferInitiatorPackageName) {
+ mService2.requestCreateSessionWithRouter2(
+ router,
+ requestId,
+ managerRequestId,
+ oldSession,
+ route,
+ sessionHints,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
@@ -580,9 +593,20 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
- int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
- mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
+ public void requestCreateSessionWithManager(
+ IMediaRouter2Manager manager,
+ int requestId,
+ RoutingSessionInfo oldSession,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
+ mService2.requestCreateSessionWithManager(
+ manager,
+ requestId,
+ oldSession,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
@@ -601,9 +625,20 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
- String sessionId, MediaRoute2Info route) {
- mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
+ public void transferToRouteWithManager(
+ IMediaRouter2Manager manager,
+ int requestId,
+ String sessionId,
+ MediaRoute2Info route,
+ UserHandle transferInitiatorUserHandle,
+ String transferInitiatorPackageName) {
+ mService2.transferToRouteWithManager(
+ manager,
+ requestId,
+ sessionId,
+ route,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
// Binder call
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c2083bd4..07b333a0fda6 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca1124f..cce66e28a8f9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02f2ab1..99c8ea93936e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@ public interface MediaSessionRecordImpl extends AutoCloseable {
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c595116afed..d4666bac89eb 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@ import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@ import android.media.session.ISessionManager;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@ public class MediaSessionService extends SystemService implements Monitor {
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@ public class MediaSessionService extends SystemService implements Monitor {
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,11 +290,31 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ notifyActivityManagerWithActiveStateChanges(record, record.isActive());
mHandler.postSessionsChanged(record);
}
}
+ private void notifyActivityManagerWithActiveStateChanges(
+ MediaSessionRecordImpl record, boolean isActive) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (isActive) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
@@ -371,8 +396,10 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +407,27 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ notifyActivityManagerWithPlaybackStateChanges(record, playbackState);
+ }
+ }
+
+ private void notifyActivityManagerWithPlaybackStateChanges(
+ MediaSessionRecordImpl record, PlaybackState playbackState) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null || playbackState == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (playbackState.shouldAllowServiceToRunInForeground()) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
}
}
@@ -543,9 +591,23 @@ public class MediaSessionService extends SystemService implements Monitor {
}
session.close();
+ notifyActivityManagerWithSessionDestroyed(session);
mHandler.postSessionsChanged(session);
}
+ private void notifyActivityManagerWithSessionDestroyed(MediaSessionRecordImpl record) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ mActivityManagerInternal.stopForegroundServiceDelegate(foregroundServiceDelegationOptions);
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 9d151c27e7c7..f7210dd1ef70 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -26,6 +27,7 @@ import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Utils;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
@@ -39,6 +41,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.media.flags.Flags;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -79,6 +82,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
+ private final Object mTransferLock = new Object();
+ @GuardedBy("mTransferLock")
+ @Nullable private volatile SessionCreationRequest mPendingTransferRequest;
+
SystemMediaRoute2Provider(Context context, UserHandle user) {
super(COMPONENT_NAME);
mIsSystemRouteProvider = true;
@@ -146,17 +153,30 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
@Override
- public void requestCreateSession(long requestId, String packageName, String routeId,
- Bundle sessionHints) {
+ public void requestCreateSession(
+ long requestId,
+ String packageName,
+ String routeId,
+ Bundle sessionHints,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
// Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
// a route ID different from the default route ID. The service should've filtered.
if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
return;
}
- if (TextUtils.equals(routeId, mSelectedRouteId)) {
- mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
- return;
+
+ if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ if (TextUtils.equals(routeId, mSelectedRouteId)) {
+ RoutingSessionInfo currentSessionInfo;
+ synchronized (mLock) {
+ currentSessionInfo = mSessionInfos.get(0);
+ }
+ mCallback.onSessionCreated(this, requestId, currentSessionInfo);
+ return;
+ }
}
synchronized (mRequestLock) {
@@ -165,10 +185,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId,
MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
}
- mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId);
+ mPendingSessionCreationRequest =
+ new SessionCreationRequest(
+ requestId,
+ routeId,
+ RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
}
- transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+ // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP.
+ transferToRoute(
+ requestId,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName,
+ SYSTEM_SESSION_ID,
+ routeId,
+ transferReason);
}
@Override
@@ -193,12 +226,31 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
@Override
- public void transferToRoute(long requestId, String sessionId, String routeId) {
+ public void transferToRoute(
+ long requestId,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName,
+ String sessionId,
+ String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason) {
if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
// The currently selected route is the default route.
Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
return;
}
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ synchronized (mTransferLock) {
+ mPendingTransferRequest =
+ new SessionCreationRequest(
+ requestId,
+ routeId,
+ transferReason,
+ transferInitiatorUserHandle,
+ transferInitiatorPackageName);
+ }
+ }
+
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
boolean isAvailableDeviceRoute =
mDeviceRouteController.getAvailableRoutes().stream()
@@ -218,6 +270,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mDeviceRouteController.transferTo(null);
mBluetoothRouteController.transferTo(routeId);
}
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+ && updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
}
@Override
@@ -322,9 +379,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
MediaRoute2Info selectedRoute = selectedDeviceRoute;
MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
+ List<String> transferableRoutes = new ArrayList<>();
+
if (selectedBtRoute != null) {
selectedRoute = selectedBtRoute;
- builder.addTransferableRoute(selectedDeviceRoute.getId());
+ transferableRoutes.add(selectedDeviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
mDefaultRoute =
@@ -337,12 +396,54 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
String routeId = route.getId();
if (!mSelectedRouteId.equals(routeId)) {
- builder.addTransferableRoute(routeId);
+ transferableRoutes.add(routeId);
}
}
}
for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
- builder.addTransferableRoute(route.getId());
+ transferableRoutes.add(route.getId());
+ }
+
+ for (String route : transferableRoutes) {
+ builder.addTransferableRoute(route);
+ }
+
+ if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+ int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
+ UserHandle transferInitiatorUserHandle = null;
+ String transferInitiatorPackageName = null;
+
+ if (oldSessionInfo != null
+ && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) {
+ transferReason = oldSessionInfo.getTransferReason();
+ transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
+ transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
+ }
+
+ synchronized (mTransferLock) {
+ if (mPendingTransferRequest != null) {
+ boolean isTransferringToTheSelectedRoute =
+ mPendingTransferRequest.isTargetRoute(selectedRoute);
+ boolean canBePotentiallyTransferred =
+ mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes);
+
+ if (isTransferringToTheSelectedRoute) {
+ transferReason = mPendingTransferRequest.mTransferReason;
+ transferInitiatorUserHandle =
+ mPendingTransferRequest.mTransferInitiatorUserHandle;
+ transferInitiatorPackageName =
+ mPendingTransferRequest.mTransferInitiatorPackageName;
+
+ mPendingTransferRequest = null;
+ } else if (!canBePotentiallyTransferred) {
+ mPendingTransferRequest = null;
+ }
+ }
+ }
+
+ builder.setTransferReason(transferReason)
+ .setTransferInitiator(
+ transferInitiatorUserHandle, transferInitiatorPackageName);
}
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
@@ -424,6 +525,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
return false;
}
+ private boolean containsSelectedRouteWithId(
+ @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) {
+ if (sessionInfo == null) {
+ return false;
+ }
+
+ List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
+
+ if (selectedRoutes.size() != 1) {
+ throw new IllegalStateException("Selected routes list should contain only 1 route id.");
+ }
+
+ String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0));
+ return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId);
+ }
+
void publishProviderState() {
updateProviderState();
notifyProviderState();
@@ -452,12 +569,47 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
private static class SessionCreationRequest {
- final long mRequestId;
- final String mRouteId;
+ private final long mRequestId;
+ @NonNull private final String mRouteId;
+
+ @RoutingSessionInfo.TransferReason private final int mTransferReason;
+
+ @NonNull private final UserHandle mTransferInitiatorUserHandle;
+ @NonNull private final String mTransferInitiatorPackageName;
+
+ SessionCreationRequest(
+ long requestId,
+ @NonNull String routeId,
+ @RoutingSessionInfo.TransferReason int transferReason,
+ @NonNull UserHandle transferInitiatorUserHandle,
+ @NonNull String transferInitiatorPackageName) {
+ mRequestId = requestId;
+ mRouteId = routeId;
+ mTransferReason = transferReason;
+ mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+ mTransferInitiatorPackageName = transferInitiatorPackageName;
+ }
+
+ private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
+ if (route2Info == null) {
+ return false;
+ }
+
+ return isTargetRoute(route2Info.getId());
+ }
+
+ private boolean isTargetRoute(@Nullable String routeId) {
+ return mRouteId.equals(routeId);
+ }
+
+ private boolean isInsideOfRoutesList(@NonNull List<String> routesList) {
+ for (String routeId : routesList) {
+ if (isTargetRoute(routeId)) {
+ return true;
+ }
+ }
- SessionCreationRequest(long requestId, String routeId) {
- this.mRequestId = requestId;
- this.mRouteId = routeId;
+ return false;
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041b8898..e4e48bdd35d1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.decreaseIndent();
+ fout.println();
fout.println("Admin restricted uids for metered data:");
fout.increaseIndent();
size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.decreaseIndent();
+ fout.println();
fout.println("Network to interfaces:");
fout.increaseIndent();
for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.decreaseIndent();
fout.println();
+ fout.print("Active notifications: ");
+ fout.println(mActiveNotifs);
+
+ fout.println();
mStatLogger.dump(fout);
mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* Build unique tag that identifies an active {@link NetworkPolicy}
* notification of a specific type, like {@link #TYPE_LIMIT}.
*/
- private String buildNotificationTag(NetworkPolicy policy, int type) {
+ private static String buildNotificationTag(NetworkPolicy policy, int type) {
return TAG + ":" + policy.template.hashCode() + ":" + type;
}
@@ -6683,5 +6689,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
public int getId() {
return mId;
}
+
+ @Override
+ public String toString() {
+ return mTag;
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a919db947593..ff415c155b35 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5472,20 +5472,13 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
+ public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
-
- if (android.app.Flags.modesApi()) {
- if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
- throw new IllegalArgumentException(String.format(
- "Mismatch between fromUser (%s) and condition.source (%s)",
- fromUser, Condition.sourceToString(condition.source)));
- }
- }
+ boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
@@ -5508,7 +5501,7 @@ public class NotificationManagerService extends SystemService {
if (android.app.Flags.modesApi()
&& fromUser
&& !isCallerSystemOrSystemUiOrShell()) {
- throw new SecurityException(String.format(
+ throw new SecurityException(TextUtils.formatSimple(
"Calling %s with fromUser == true is only allowed for system", method));
}
}
@@ -5617,7 +5610,8 @@ public class NotificationManagerService extends SystemService {
return !isCompatChangeEnabled
|| isCallerSystemOrSystemUi()
|| hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
- AssociationRequest.DEVICE_PROFILE_WATCH);
+ Set.of(AssociationRequest.DEVICE_PROFILE_WATCH,
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION));
}
private void enforcePolicyAccess(String pkg, String method) {
@@ -7018,12 +7012,14 @@ public class NotificationManagerService extends SystemService {
return false;
}
- final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE);
+ final boolean hasBitmap = n.extras.containsKey(Notification.EXTRA_PICTURE)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE) != null;
if (hasBitmap) {
return true;
}
- final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON);
+ final boolean hasIcon = n.extras.containsKey(Notification.EXTRA_PICTURE_ICON)
+ && n.extras.getParcelable(Notification.EXTRA_PICTURE_ICON) != null;
if (hasIcon) {
return true;
}
@@ -7039,9 +7035,10 @@ public class NotificationManagerService extends SystemService {
if (!isBigPictureWithBitmapOrIcon(r.getNotification())) {
return;
}
- // Remove Notification object's reference to picture bitmap or URI
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE);
- r.getNotification().extras.remove(Notification.EXTRA_PICTURE_ICON);
+ // Remove Notification object's reference to picture bitmap or URI. Leave the extras set to
+ // null to avoid crashing apps that came to expect them to be present but null.
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE, null);
+ r.getNotification().extras.putParcelable(Notification.EXTRA_PICTURE_ICON, null);
// Make Notification silent
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -10750,6 +10747,14 @@ public class NotificationManagerService extends SystemService {
final String key = record.getSbn().getKey();
final NotificationListenerService.Ranking ranking =
new NotificationListenerService.Ranking();
+ ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
+ ArrayList<CharSequence> smartReplies = record.getSmartReplies();
+ if (redactSensitiveNotificationsFromUntrustedListeners()
+ && !mListeners.isUidTrusted(info.uid)
+ && mListeners.hasSensitiveContent(record)) {
+ smartActions = null;
+ smartReplies = null;
+ }
ranking.populate(
key,
rankings.size(),
@@ -10767,8 +10772,8 @@ public class NotificationManagerService extends SystemService {
record.isHidden(),
record.getLastAudiblyAlertedMs(),
record.getSound() != null || record.getVibration() != null,
- record.getSystemGeneratedSmartActions(),
- record.getSmartReplies(),
+ smartActions,
+ smartReplies,
record.canBubble(),
record.isTextChanged(),
record.isConversation(),
@@ -10797,7 +10802,7 @@ public class NotificationManagerService extends SystemService {
}
private boolean hasCompanionDevice(String pkg, @UserIdInt int userId,
- @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) {
+ @Nullable Set</* @AssociationRequest.DeviceProfile */ String> withDeviceProfiles) {
if (mCompanionManager == null) {
mCompanionManager = getCompanionManager();
}
@@ -10809,7 +10814,7 @@ public class NotificationManagerService extends SystemService {
try {
List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId);
for (AssociationInfo association : associations) {
- if (withDeviceProfile == null || withDeviceProfile.equals(
+ if (withDeviceProfiles == null || withDeviceProfiles.contains(
association.getDeviceProfile())) {
return true;
}
@@ -11518,20 +11523,16 @@ public class NotificationManagerService extends SystemService {
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
String pkgName = getPackageName(pkgOrComponent);
if (redactSensitiveNotificationsFromUntrustedListeners()) {
- try {
- int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
- if (!enabled) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.remove(uid);
- }
+ int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+ if (!enabled && uid >= 0) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.remove(uid);
}
- if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.add(uid);
- }
+ }
+ if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.add(uid);
}
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
}
}
@@ -11951,8 +11952,10 @@ public class NotificationManagerService extends SystemService {
for (final ManagedServiceInfo info : getServices()) {
boolean isTrusted = isUidTrusted(info.uid);
- boolean sendRedacted = isNewSensitive && !isTrusted;
- boolean sendOldRedacted = isOldSensitive && !isTrusted;
+ boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isNewSensitive && !isTrusted;
+ boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isOldSensitive && !isTrusted;
boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12051,7 +12054,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
if (!redactSensitiveNotificationsFromUntrustedListeners()) {
- return sbn;
+ throw new RuntimeException("redactStatusBarNotification called while flag is off");
}
ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12223,6 +12226,7 @@ public class NotificationManagerService extends SystemService {
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
+
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12246,7 +12250,6 @@ public class NotificationManagerService extends SystemService {
if (notifyThisListener || !isHiddenRankingUpdate) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(
serviceInfo);
-
mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
}
}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 55d8a0fd1fa1..8e79922a996a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -41,3 +41,12 @@ flag {
description: "This flag controls the fix for notifications autogroup summary icon updates"
bug: "227693160"
}
+
+flag {
+ name: "sensitive_notification_app_protection"
+ namespace: "systemui"
+ description: "This flag controls the sensitive notification app protections while screen sharing"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b70419b74..c920ca89b75e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@ final class DeletePackageHelper {
int returnCodeOfChild;
for (int childId : childUserIds) {
if (childId == userId) continue;
+ if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+ continue;
+ }
// If package is not present in child then don't attempt to delete.
if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 3b9f9c804e27..41d01762505d 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -46,11 +46,11 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.WatchedArrayMap;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 65bfb2f258eb..b638d306544c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -146,6 +146,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -154,6 +155,7 @@ import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
@@ -178,7 +180,6 @@ import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.Permission;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -672,6 +673,9 @@ final class InstallPackageHelper {
if (pkgSetting == null || pkgSetting.getPkg() == null) {
return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
}
+ if (instantApp && (pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp())) {
+ return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
+ }
if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
// only allow the existing package to be used if it's installed as a full
// application for at least one user
@@ -1167,8 +1171,9 @@ final class InstallPackageHelper {
parseFlags);
archivedPackage = request.getPackageLite().getArchivedPackage();
}
- } catch (PackageManagerException | PackageParserException e) {
- throw new PrepareFailure("Failed parse during installPackageLI", e);
+ } catch (PackageParserException e) {
+ throw new PrepareFailure(e.error,
+ ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -2858,14 +2863,17 @@ final class InstallPackageHelper {
mPm.notifyPackageChanged(packageName, request.getAppId());
}
- for (int userId : firstUserIds) {
- // Apply restricted settings on potentially dangerous packages. Needs to happen
- // after appOpsManager is notified of the new package
- if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
- || request.getPackageSource()
- == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
- enableRestrictedSettings(packageName, request.getAppId(), userId);
- }
+ // Apply restricted settings on potentially dangerous packages. Needs to happen
+ // after appOpsManager is notified of the new package
+ if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ || request.getPackageSource()
+ == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+ final int appId = request.getAppId();
+ mPm.mHandler.post(() -> {
+ for (int userId : firstUserIds) {
+ enableRestrictedSettings(packageName, appId, userId);
+ }
+ });
}
// Log current value of "unknown sources" setting
@@ -3680,6 +3688,8 @@ final class InstallPackageHelper {
final ParsedPackage parsedPackage;
try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(e.error, e.getMessage(), e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 187cadaf4d46..e970d2c79b05 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -51,8 +51,8 @@ import android.util.Slog;
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.util.Preconditions;
-import com.android.server.pm.parsing.PackageParser2;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2942bbb86e62..cbd65a4eb936 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,6 +111,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.TypedXmlPullParser;
@@ -120,7 +121,6 @@ import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.utils.RequestThrottle;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1d5b8c3ed22a..b7deef0e7647 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,7 @@ import com.android.internal.app.ResolverActivity;
import com.android.internal.content.F2fsUtils;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedInstrumentation;
@@ -221,8 +222,8 @@ import com.android.server.pm.dex.ArtUtils;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
import com.android.server.pm.local.PackageManagerLocalImpl;
+import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerService;
@@ -606,6 +607,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
private final boolean mIsUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
+ // If mIsUpgrade == true, contains the prior SDK version, else -1.
+ private final int mPriorSdkVersion;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -1698,7 +1701,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
() -> LocalServices.getService(UserManagerInternal.class)),
(i, pm) -> new DisplayMetrics(),
(i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(),
- pm.mCacheDir,
+ new PackageCacher(pm.mCacheDir),
pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */,
(i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), null,
pm.mPackageParserCallback) /* scanningPackageParserProducer */,
@@ -1890,6 +1893,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
+ mPriorSdkVersion = testParams.priorSdkVersion;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2229,7 +2233,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
"Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
-
+ mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
mInjector.getSystemPartitions());
@@ -7098,6 +7102,12 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
visibilityAllowList, mHandler);
}
+
+ @Override
+ public boolean isUpgradingFromLowerThan(int sdkVersion) {
+ final boolean isUpgrading = mPriorSdkVersion != -1;
+ return isUpgrading && mPriorSdkVersion < sdkVersion;
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index ebf1c04bee12..049737d42f51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -27,12 +27,12 @@ import android.os.incremental.IncrementalManager;
import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.SystemConfig;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.resolution.ComponentResolver;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 655b9c93d9dd..2d797187b7f1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -31,10 +31,10 @@ import android.util.DisplayMetrics;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -65,6 +65,7 @@ public final class PackageManagerServiceTestParams {
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreQupgrade;
+ public int priorSdkVersion = -1;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 243fb16b19ae..5724ee0d94e9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1085,8 +1085,14 @@ class PackageManagerShellCommand extends ShellCommand {
// the sdk or package name along with optional additional information based on opt.
final Map<String, List<String>> out = new HashMap<>();
for (int userId : userIds) {
- final int translatedUserId =
+ final int translatedUserId;
+ try {
+ translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
+ } catch (RuntimeException ex) {
+ getErrPrintWriter().println("Error: " + ex.toString());
+ continue;
+ }
@SuppressWarnings("unchecked") final ParceledListSlice<PackageInfo> slice =
mInterface.getInstalledPackages(getFlags, translatedUserId);
final List<PackageInfo> packages = slice.getList();
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 15d2fdc35f2b..1fe49c7d5834 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -41,10 +41,11 @@ import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.InstallLocationUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.rollback.RollbackManagerInternal;
import java.io.File;
@@ -399,7 +400,7 @@ final class PackageSessionVerifier {
try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
File apexFile = new File(apexInfo.modulePath);
parsedPackage = packageParser.parsePackage(apexFile, 0, false);
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
"Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 1089ac943802..051163925b16 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,9 +22,10 @@ import android.os.Process;
import android.os.Trace;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.util.ConcurrentUtils;
-import com.android.server.pm.parsing.PackageParser2;
import java.io.File;
import java.util.concurrent.ArrayBlockingQueue;
@@ -125,6 +126,10 @@ class ParallelPackageParser {
@VisibleForTesting
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
- return mPackageParser.parsePackage(scanFile, parseFlags, true);
+ try {
+ return mPackageParser.parsePackage(scanFile, parseFlags, true);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(e.error, e.getMessage(), e);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae2734c0db..b286b12dcf7d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.PatternMatcher;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.Trace;
@@ -3189,6 +3190,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pkg.isScannedAsStoppedSystemApp());
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
+
+ serializer.attributeBoolean(null, "isSdkLibrary",
+ pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
} else {
serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
}
@@ -4039,10 +4043,12 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
+ boolean isSdkLibrary = false;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
appId = parseAppId(parser);
+ isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
sharedUserAppId = parseSharedUserAppId(parser);
codePathStr = parser.getAttributeValue(null, "codePath");
@@ -4157,7 +4163,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no codePath at "
+ parser.getPositionDescription());
- } else if (appId > 0) {
+ } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+ && Flags.disallowSdkLibsToBeApps())) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 79c9c8ec8b0b..b6267c499c07 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,6 +28,7 @@ import android.system.StructStat;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.IPackageCacher;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.ApexManager;
@@ -39,7 +40,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
-public class PackageCacher {
+public class PackageCacher implements IPackageCacher {
private static final String TAG = "PackageCacher";
@@ -162,6 +163,7 @@ public class PackageCacher {
* Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
* or {@code null} if no cached result exists.
*/
+ @Override
public ParsedPackage getCachedResult(File packageFile, int flags) {
final String cacheKey = getCacheKey(packageFile, flags);
final File cacheFile = new File(mCacheDir, cacheKey);
@@ -192,6 +194,7 @@ public class PackageCacher {
/**
* Caches the parse result for {@code packageFile} with flags {@code flags}.
*/
+ @Override
public void cacheResult(File packageFile, int flags, ParsedPackage parsed) {
try {
final String cacheKey = getCacheKey(packageFile, flags);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
new file mode 100644
index 000000000000..03a7a37a550c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageManagerService;
+
+import java.util.Set;
+
+public class PackageParserUtils {
+ /**
+ * For parsing inside the system server but outside of {@link PackageManagerService}.
+ * Generally used for parsing information in an APK that hasn't been installed yet.
+ *
+ * This must be called inside the system process as it relies on {@link ServiceManager}.
+ */
+ @NonNull
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static PackageParser2 forParsingFileWithDefaults() {
+ IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
+ null /* cacheDir */, new PackageParser2.Callback() {
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+ try {
+ return platformCompat.isChangeEnabled(changeId, appInfo);
+ } catch (Exception e) {
+ // This shouldn't happen, but assume enforcement if it does
+ Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean hasFeature(String feature) {
+ // Assume the device doesn't support anything. This will affect permission parsing
+ // and will force <uses-permission/> declarations to include all requiredNotFeature
+ // permissions and exclude all requiredFeature permissions. This mirrors the old
+ // behavior.
+ return false;
+ }
+
+ @Override
+ public Set<String> getHiddenApiWhitelistedApps() {
+ return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+ }
+
+ @Override
+ public Set<String> getInstallConstraintsAllowlist() {
+ return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
index b531b0eff854..611e4ed0e1f4 100644
--- a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
+++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
@@ -76,6 +76,18 @@ class DeferredKeyActionExecutor {
getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable();
}
+ /**
+ * Clears all the queued action for given key code.
+ *
+ * @param keyCode the key code whose queued actions will be cleared.
+ */
+ public void cancelQueuedAction(int keyCode) {
+ TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode);
+ if (actionsBuffer != null) {
+ actionsBuffer.clear();
+ }
+ }
+
private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) {
TimedActionsBuffer buffer = mBuffers.get(keyCode);
if (buffer == null || buffer.getDownTime() != downTime) {
@@ -146,6 +158,10 @@ class DeferredKeyActionExecutor {
mActions.clear();
}
+ void clear() {
+ mActions.clear();
+ }
+
void dump(String prefix, PrintWriter pw) {
if (mExecutable) {
pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e8b54d58c907..3000a1c5c043 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -103,6 +103,7 @@ import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IUiModeManager;
@@ -584,6 +585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private int mLongPressOnStemPrimaryBehavior;
private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp;
+ // The focused task at the time when the first STEM_PRIMARY key was released. This can only
+ // be accessed from the looper thread.
+ private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp;
+
private boolean mHandleVolumeKeysInWM;
private boolean mPendingKeyguardOccluded;
@@ -2135,12 +2140,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static class Injector {
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
- private final Looper mLooper;
- Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
+ Injector(Context context, WindowManagerFuncs funcs) {
mContext = context;
mWindowManagerFuncs = funcs;
- mLooper = looper;
}
Context getContext() {
@@ -2152,7 +2155,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
Looper getLooper() {
- return mLooper;
+ return Looper.myLooper();
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -2195,7 +2198,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
public void init(Context context, WindowManagerFuncs funcs) {
- init(new Injector(context, funcs, Looper.myLooper()));
+ init(new Injector(context, funcs));
}
@VisibleForTesting
@@ -2723,7 +2726,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
void onPress(long downTime, int unusedDisplayId) {
- if (mShouldEarlyShortPressOnStemPrimary) {
+ if (shouldHandleStemPrimaryEarlyShortPress()) {
return;
}
// Short-press should be triggered only if app doesn't handle it.
@@ -2747,6 +2750,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (count == 3
&& mTriplePressOnStemPrimaryBehavior
== TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) {
+ // Cancel any queued actions for current key code to prevent them from being
+ // launched after a11y layer enabled. If the action happens early,
+ // undoEarlySinglePress will make sure the correct task is on top.
+ mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+ undoEarlySinglePress();
stemPrimaryPress(count);
} else {
// Other multi-press gestures should be triggered only if app doesn't handle it.
@@ -2755,6 +2763,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ /**
+ * This method undo the previously launched early-single-press action by bringing the
+ * focused task before launching early-single-press back to top.
+ */
+ private void undoEarlySinglePress() {
+ if (shouldHandleStemPrimaryEarlyShortPress()
+ && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) {
+ try {
+ mActivityManagerService.startActivityFromRecents(
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null);
+ } catch (RemoteException | IllegalArgumentException e) {
+ Slog.e(
+ TAG,
+ "Failed to start task "
+ + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId
+ + " from recents",
+ e);
+ }
+ }
+ }
+
@Override
void onKeyUp(long eventTime, int count, int unusedDisplayId) {
if (count == 1) {
@@ -2763,15 +2792,49 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// It is possible that we may navigate away from this task before the double
// press is detected, as a result of the first press, so we save the current
// most recent task before that happens.
+ // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP
mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp =
mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
- if (mShouldEarlyShortPressOnStemPrimary) {
+
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp = null;
+
+ if (shouldHandleStemPrimaryEarlyShortPress()) {
// Key-up gesture should be triggered only if app doesn't handle it.
mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1));
+ KeyEvent.KEYCODE_STEM_PRIMARY,
+ eventTime,
+ () -> {
+ // Save the info of the focused task on screen. This may be used
+ // later to bring the current focused task back to top. For
+ // example, stem primary triple press enables the A11y interface
+ // on top of the current focused task. When early single press is
+ // enabled for stem primary, the focused task could change to
+ // something else upon first key up event. In that case, we will
+ // bring the task recorded by this variable back to top. Then, start
+ // A11y interface.
+ try {
+ mFocusedTaskInfoOnStemPrimarySingleKeyUp =
+ mActivityManagerService.getFocusedRootTaskInfo();
+ } catch (RemoteException e) {
+ Slog.e(
+ TAG,
+ "StemPrimaryKeyRule: onKeyUp: error while getting "
+ + "focused task "
+ + "info.",
+ e);
+ }
+
+ stemPrimaryPress(1);
+ });
}
}
}
+
+ // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button.
+ private boolean shouldHandleStemPrimaryEarlyShortPress() {
+ return mShouldEarlyShortPressOnStemPrimary
+ && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
+ }
}
private void initSingleKeyGestureRules(Looper looper) {
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7c833cbe57d4..6f754391f1ca 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -481,6 +481,11 @@ public final class HintManagerService extends SystemService {
protected long mTargetDurationNanos;
protected boolean mUpdateAllowed;
protected int[] mNewThreadIds;
+ protected boolean mPowerEfficient;
+
+ private enum SessionModes {
+ POWER_EFFICIENCY,
+ };
protected AppHintSession(
int uid, int pid, int[] threadIds, IBinder token,
@@ -492,6 +497,7 @@ public final class HintManagerService extends SystemService {
mHalSessionPtr = halSessionPtr;
mTargetDurationNanos = durationNanos;
mUpdateAllowed = true;
+ mPowerEfficient = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowed(allowed);
try {
@@ -634,6 +640,9 @@ public final class HintManagerService extends SystemService {
}
Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
+ " greater than zero.");
+ if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
+ mPowerEfficient = enabled;
+ }
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
}
@@ -653,6 +662,12 @@ public final class HintManagerService extends SystemService {
}
}
+ public boolean isPowerEfficient() {
+ synchronized (this) {
+ return mPowerEfficient;
+ }
+ }
+
void validateWorkDuration(WorkDuration workDuration) {
if (DEBUG) {
Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
@@ -718,6 +733,7 @@ public final class HintManagerService extends SystemService {
pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
+ pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1ce87a7b6af3..145eb3b8464c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -125,6 +125,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -683,6 +684,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
+ // TODO(b/317000737): Replace it with visibility states lookup.
int mTransitionChangeFlags;
/** Whether we need to setup the animation to animate only within the letterbox. */
@@ -5468,8 +5470,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Defer committing visibility until transition starts.
if (isCollecting) {
// It may be occluded by the activity above that calls convertFromTranslucent().
- if (!visible && mTransitionController.inPlayingTransition(this)) {
- mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ // Or it may be restoring transient launch to invisible when finishing transition.
+ if (!visible) {
+ if (mTransitionController.inPlayingTransition(this)) {
+ mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+ } else if (mTransitionController.inFinishingTransition(this)) {
+ mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ }
}
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 908c49eb8580..dbae29bd37c9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -62,6 +62,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIV
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
import static android.provider.Settings.System.FONT_SCALE;
+import static android.service.controls.flags.Flags.homePanelDream;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1501,14 +1502,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
a.exported = true;
a.name = DreamActivity.class.getName();
a.enabled = true;
- a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
+ if (homePanelDream()) {
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_TASK;
+ } else {
+ a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+ }
+
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 68d13cd6789e..6ed896751bb3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -91,6 +91,13 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Non-zero if this controller is triggered by shell transition. */
private final @TransitionOp int mTransitionOp;
+ /**
+ * Whether {@link #setupStartTransaction} is called when the transition is ready.
+ * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state
+ * before the transition is ready, then this controller should be finished.
+ */
+ private boolean mIsStartTransactionPrepared;
+
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
@@ -226,7 +233,8 @@ class AsyncRotationController extends FadeAnimationController implements Consume
void updateTargetWindows() {
if (mTransitionOp == OP_LEGACY) return;
if (!mIsStartTransactionCommitted) {
- if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared)
+ && !mDisplayContent.hasTopFixedRotationLaunchingApp()
&& !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
Slog.d(TAG, "Cancel for no change");
mDisplayContent.finishAsyncRotationIfPossible();
@@ -401,9 +409,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
synchronized (mService.mGlobalLock) {
- Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
- ? " start transaction is not committed" : mTargetWindowTokens));
+ final String reason;
if (!mIsStartTransactionCommitted) {
+ if (!mIsStartTransactionPrepared) {
+ reason = "setupStartTransaction is not called";
+ } else {
+ reason = "start transaction is not committed";
+ }
+ } else {
+ reason = "unfinished windows " + mTargetWindowTokens;
+ }
+ Slog.i(TAG, "Async rotation timeout: " + reason);
+ if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) {
// The transaction commit timeout will be handled by:
// 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
// apply the start transaction of transition.
@@ -558,6 +575,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
}
}
});
+ mIsStartTransactionPrepared = true;
}
/** Called when the start transition is ready, but it is not applied in time. */
@@ -577,6 +595,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Called when the transition by shell is done. */
void onTransitionFinished() {
if (mTransitionOp == OP_CHANGE) {
+ if (mTargetWindowTokens.isEmpty()) {
+ // If nothing was handled, then complete with the transition.
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
// With screen rotation animation, the windows are always faded in when they are drawn.
// Because if they are drawn fast enough, the fade animation should not be observable.
return;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4929df8061b2..eed46fee1ae1 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -36,6 +36,7 @@ import static com.android.window.flags.Flags.balShowToastsBlocked;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -152,36 +153,25 @@ public class BackgroundActivityStartController {
static final int BAL_ALLOW_SDK_SANDBOX = 10;
static String balCodeToString(@BalCode int balCode) {
- switch (balCode) {
- case BAL_ALLOW_ALLOWLISTED_COMPONENT:
- return "BAL_ALLOW_ALLOWLISTED_COMPONENT";
- case BAL_ALLOW_ALLOWLISTED_UID:
- return "BAL_ALLOW_ALLOWLISTED_UID";
- case BAL_ALLOW_DEFAULT:
- return "BAL_ALLOW_DEFAULT";
- case BAL_ALLOW_FOREGROUND:
- return "BAL_ALLOW_FOREGROUND";
- case BAL_ALLOW_GRACE_PERIOD:
- return "BAL_ALLOW_GRACE_PERIOD";
- case BAL_ALLOW_PENDING_INTENT:
- return "BAL_ALLOW_PENDING_INTENT";
- case BAL_ALLOW_PERMISSION:
- return "BAL_ALLOW_PERMISSION";
- case BAL_ALLOW_SAW_PERMISSION:
- return "BAL_ALLOW_SAW_PERMISSION";
- case BAL_ALLOW_SDK_SANDBOX:
- return "BAL_ALLOW_SDK_SANDBOX";
- case BAL_ALLOW_VISIBLE_WINDOW:
- return "BAL_ALLOW_VISIBLE_WINDOW";
- case BAL_BLOCK:
- return "BAL_BLOCK";
- default:
- throw new IllegalArgumentException("Unexpected value: " + balCode);
- }
+ return switch (balCode) {
+ case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
+ case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+ case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
+ case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
+ case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
+ case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
+ case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
+ case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
+ case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+ case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
+ case BAL_BLOCK -> "BAL_BLOCK";
+ default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
+ };
}
@GuardedBy("mService.mGlobalLock")
- private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+ private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
+ new HashMap<>();
@GuardedBy("mService.mGlobalLock")
private FinishedActivityEntry mTopFinishedActivity = null;
@@ -467,9 +457,8 @@ public class BackgroundActivityStartController {
return !blocks();
}
- BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+ void setOnlyCreatorAllows(boolean onlyCreatorAllows) {
mOnlyCreatorAllows = onlyCreatorAllows;
- return this;
}
boolean onlyCreatorAllows() {
@@ -481,10 +470,6 @@ public class BackgroundActivityStartController {
return this;
}
- private boolean isBasedOnRealCaller() {
- return mBasedOnRealCaller;
- }
-
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(balCodeToString(mCode));
@@ -583,15 +568,14 @@ public class BackgroundActivityStartController {
BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
if (!state.hasRealCaller()) {
- BalVerdict resultForRealCaller = null; // nothing to compute
if (resultForCaller.allows()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Background activity start allowed. "
- + state.dump(resultForCaller, resultForRealCaller));
+ + state.dump(resultForCaller, resultForCaller));
}
return statsLog(resultForCaller, state);
}
- return abortLaunch(state, resultForCaller, resultForRealCaller);
+ return abortLaunch(state, resultForCaller, resultForCaller);
}
// The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -653,7 +637,7 @@ public class BackgroundActivityStartController {
+ " if the PI creator upgrades target_sdk to 35+"
+ " AND the PI sender upgrades target_sdk to 34+! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
// return the realCaller result for backwards compatibility
return statsLog(resultForRealCaller, state);
}
@@ -679,7 +663,7 @@ public class BackgroundActivityStartController {
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
return statsLog(resultForCaller, state);
}
Slog.wtf(TAG,
@@ -696,7 +680,7 @@ public class BackgroundActivityStartController {
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalRiskToast("BAL would be blocked", state);
+ showBalRiskToast();
return statsLog(resultForRealCaller, state);
}
Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -712,7 +696,7 @@ public class BackgroundActivityStartController {
BalVerdict resultForRealCaller) {
Slog.w(TAG, "Background activity launch blocked! "
+ state.dump(resultForCaller, resultForRealCaller));
- showBalBlockedToast("BAL blocked", state);
+ showBalBlockedToast();
return statsLog(BalVerdict.BLOCK, state);
}
@@ -910,7 +894,7 @@ public class BackgroundActivityStartController {
/**
* Check if the app allows BAL.
- *
+ * <p>
* See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
* String, int, boolean, boolean, boolean, long, long, long)} for details on the
* exceptions.
@@ -1104,19 +1088,15 @@ public class BackgroundActivityStartController {
return true;
}
- private void showBalBlockedToast(String toastText, BalState state) {
+ private void showBalBlockedToast() {
if (balShowToastsBlocked()) {
- showToast(toastText
- + " caller:" + state.mCallingPackage
- + " realCaller:" + state.mRealCallingPackage);
+ showToast("BAL blocked. go/debug-bal");
}
}
- private void showBalRiskToast(String toastText, BalState state) {
+ private void showBalRiskToast() {
if (balShowToasts()) {
- showToast(toastText
- + " caller:" + state.mCallingPackage
- + " realCaller:" + state.mRealCallingPackage);
+ showToast("BAL allowed in compat mode. go/debug-bal");
}
}
@@ -1281,7 +1261,7 @@ public class BackgroundActivityStartController {
* 2. Or top of an adjacent task fragment to (1)
* <p>
* The 'sourceRecord' can be considered top even if it is 'finishing'
- *
+ * <p>
* Returns a class where the elements are:
* <pre>
* shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
@@ -1344,7 +1324,7 @@ public class BackgroundActivityStartController {
/**
* Determines if a source is allowed to add or remove activities from the task,
* if the current ActivityRecord is above it in the stack
- *
+ * <p>
* A transition is blocked ({@code false} returned) if all of the following are met:
* <pre>
* 1. The source activity and the current activity record belong to different apps
@@ -1489,8 +1469,8 @@ public class BackgroundActivityStartController {
if (code == BAL_ALLOW_PENDING_INTENT
&& (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
- String activityName =
- intent != null ? intent.getComponent().flattenToShortString() : "";
+ String activityName = intent != null
+ ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
activityName,
BAL_ALLOW_PENDING_INTENT,
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f020bfa8cbc7..3117db5f27f0 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -43,6 +43,7 @@ import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -3067,6 +3068,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
flags |= FLAG_TASK_LAUNCHING_BEHIND;
}
+ if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION)
+ == FLAGS_IS_OCCLUDED_NO_ANIMATION) {
+ flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+ }
}
if (task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9c21e4c0121e..32638e0afc68 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -202,7 +202,6 @@ import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.PowerManager;
-import android.os.PowerManager.WakeReason;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -702,11 +701,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private final Region mTapExcludeRegion = new Region();
- /**
- * Used for testing because the real PowerManager is final.
- */
- private PowerManagerWrapper mPowerManagerWrapper;
-
private static final StringBuilder sTmpSB = new StringBuilder();
/**
@@ -1061,34 +1055,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mOnBackInvokedCallbackInfo;
}
- interface PowerManagerWrapper {
- void wakeUp(long time, @WakeReason int reason, String details);
-
- boolean isInteractive();
-
- }
-
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
- this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
- ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
- @Override
- public void wakeUp(long time, @WakeReason int reason, String details) {
- service.mPowerManager.wakeUp(time, reason, details);
- }
-
- @Override
- public boolean isInteractive() {
- return service.mPowerManager.isInteractive();
- }
- });
- }
-
- WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
- int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
- PowerManagerWrapper powerManagerWrapper) {
super(service);
mTmpTransaction = service.mTransactionFactory.get();
mSession = s;
@@ -1106,7 +1075,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
- mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
mActivityRecord != null
@@ -2831,12 +2799,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn();
if (allowTheaterMode && canTurnScreenOn
- && (mWmService.mAtmService.isDreaming()
- || !mPowerManagerWrapper.isInteractive())) {
+ && (mWmService.mAtmService.isDreaming()
+ || !mWmService.mPowerManager.isInteractive())) {
if (DEBUG_VISIBILITY || DEBUG_POWER) {
Slog.v(TAG, "Relayout window turning screen on: " + this);
}
- mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
+ mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
}
@@ -5669,6 +5637,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Skip sync for invisible app windows which are not managed by activity lifecycle.
return false;
}
+ if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+ && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+ && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+ // Skip sync for invisible app windows which are not managed by activity lifecycle.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 061fe0fc88d9..cc08488742b2 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -31,5 +31,5 @@ per-file com_android_server_vibrator_* = file:/services/core/java/com/android/se
per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+# Bug component : 158088 = per-file *AnrTimer*
+per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
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 adbd3c9c5096..3cbceec5b9cd 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -631,8 +631,8 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <xs:element name="mode" type="xs:string" minOccurs="0"/>
- <xs:element name="setting" type="xs:string" minOccurs="0"/>
+ <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/>
+ <xs:element name="setting" type="AutoBrightnessSettingName" minOccurs="0"/>
</xs:complexType>
<!-- Represents a point in the display brightness mapping, representing the lux level from the
@@ -765,4 +765,23 @@
</xs:element>
</xs:sequence>
</xs:complexType>
+
+ <!-- Predefined type names as defined by
+ AutomaticBrightnessController.AutomaticBrightnessMode -->
+ <xs:simpleType name="AutoBrightnessModeName">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="idle"/>
+ <xs:enumeration value="doze"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- Predefined auto-brighntess settings -->
+ <xs:simpleType name="AutoBrightnessSettingName">
+ <xs:restriction base="xs:string">
+ <xs:enumeration value="dim"/>
+ <xs:enumeration value="normal"/>
+ <xs:enumeration value="bright"/>
+ </xs:restriction>
+ </xs:simpleType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 98c95edef237..79ea274e2fca 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -16,6 +16,20 @@ package com.android.server.display.config {
method public void setEnabled(boolean);
}
+ public enum AutoBrightnessModeName {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName _default;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName doze;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle;
+ }
+
+ public enum AutoBrightnessSettingName {
+ method public String getRawName();
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName bright;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName dim;
+ enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName normal;
+ }
+
public class BlockingZoneConfig {
ctor public BlockingZoneConfig();
method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
@@ -219,11 +233,11 @@ package com.android.server.display.config {
public class LuxToBrightnessMapping {
ctor public LuxToBrightnessMapping();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
- method public String getMode();
- method public String getSetting();
+ method public com.android.server.display.config.AutoBrightnessModeName getMode();
+ method public com.android.server.display.config.AutoBrightnessSettingName getSetting();
method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
- method public void setMode(String);
- method public void setSetting(String);
+ method public void setMode(com.android.server.display.config.AutoBrightnessModeName);
+ method public void setSetting(com.android.server.display.config.AutoBrightnessSettingName);
}
public class NitsMap {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7571d0..1185a4e4f93b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@ public final class SystemServer implements Dumpable {
// AdServicesManagerService (PP API service)
t.traceBegin("StartAdServicesManagerService");
- SystemService adServices = mSystemServiceManager
- .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
- if (adServices instanceof Dumpable) {
- mDumper.addDumpable((Dumpable) adServices);
- }
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
t.traceEnd();
// OnDevicePersonalizationSystemService
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 03e45a27390f..71f5c754f22f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -58,6 +58,8 @@ import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.permission.CompatibilityPermissionInfo;
@@ -84,7 +86,7 @@ import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
@@ -185,7 +187,7 @@ public class PackageParserTest {
@Test
public void test_serializePackage() throws Exception {
- try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
+ try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) {
AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
true /* useCaches */).hideAsFinal();
@@ -363,7 +365,7 @@ public class PackageParserTest {
actualDisplayCategory = activity.getRequiredDisplayCategory();
}
}
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
assertThat(e.getMessage()).contains(
"requiredDisplayCategory attribute can only consist"
+ " of alphanumeric characters, '_', and '.'");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
index 8a74e24a3810..376124049c4c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -21,8 +21,8 @@ import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.TestPackageParser2;
import junit.framework.Assert;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index b63950c10a18..a28b28a49162 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -38,6 +38,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivityUtils;
import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -45,7 +46,6 @@ import com.android.internal.pm.pkg.component.ParsedIntentInfo;
import com.android.internal.pm.pkg.component.ParsedPermission;
import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerException;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.test.service.server.R;
@@ -608,7 +608,7 @@ public class PackageParserLegacyCoreTest {
try {
parsePackage(filename, resId, x -> x);
expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
- } catch (PackageManagerException expected) {
+ } catch (PackageParserException expected) {
expect.that(expected.error).isEqualTo(result);
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 98af63c65e90..1d668cd3e8f2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -18,8 +18,8 @@ package com.android.server.pm.parsing
import android.content.pm.PackageManager
import android.platform.test.annotations.Postsubmit
+import com.android.internal.pm.parsing.PackageParserException
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
-import com.android.server.pm.PackageManagerException
import com.android.server.pm.PackageManagerService
import com.android.server.pm.PackageManagerServiceUtils
import java.io.File
@@ -39,7 +39,7 @@ import org.junit.rules.TemporaryFolder
@Postsubmit
class SystemPartitionParseTest {
- private val parser = PackageParser2.forParsingFileWithDefaults()
+ private val parser = PackageParserUtils.forParsingFileWithDefaults()
@get:Rule
val tempFolder = TemporaryFolder()
@@ -86,7 +86,7 @@ class SystemPartitionParseTest {
}
}
.mapNotNull { it.exceptionOrNull() }
- .filterNot { (it as? PackageManagerException)?.error ==
+ .filterNot { (it as? PackageParserException)?.error ==
PackageManager.INSTALL_PARSE_FAILED_SKIPPED }
if (exceptions.isEmpty()) return
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index c5a1ba1f6758..fb73aff44c64 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
import static org.junit.Assert.assertEquals;
@@ -29,21 +30,22 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.BrightnessConfiguration;
import android.os.PowerManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
import android.util.MathUtils;
import android.util.Spline;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import java.util.Arrays;
@@ -154,15 +156,23 @@ public class BrightnessMappingStrategyTest {
private static final float TOLERANCE = 0.0001f;
- @Mock
- DisplayWhiteBalanceController mMockDwbc;
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Before
+ public void setUp() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL);
+ }
@Test
public void testSimpleStrategyMappingAtControlPoints() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 0; i < LUX_LEVELS.length; i++) {
assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
@@ -171,10 +181,10 @@ public class BrightnessMappingStrategyTest {
@Test
public void testSimpleStrategyMappingBetweenControlPoints() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", simple);
for (int i = 1; i < LUX_LEVELS.length; i++) {
final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -186,10 +196,10 @@ public class BrightnessMappingStrategyTest {
@Test
public void testSimpleStrategyIgnoresNewConfiguration() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
final float[] lux = { 0f, 1f };
final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -202,10 +212,10 @@ public class BrightnessMappingStrategyTest {
@Test
public void testSimpleStrategyIgnoresNullConfiguration() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
strategy.setBrightnessConfiguration(null);
final int n = DISPLAY_LEVELS.length;
@@ -216,11 +226,11 @@ public class BrightnessMappingStrategyTest {
@Test
public void testPhysicalStrategyMappingAtControlPoints() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
.build();
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
for (int i = 0; i < LUX_LEVELS.length; i++) {
final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -235,11 +245,11 @@ public class BrightnessMappingStrategyTest {
@Test
public void testPhysicalStrategyMappingBetweenControlPoints() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", physical);
Spline brightnessToNits =
Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -254,11 +264,11 @@ public class BrightnessMappingStrategyTest {
@Test
public void testPhysicalStrategyUsesNewConfigurations() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
.build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
final float[] lux = {0f, 1f};
final float[] nits = {
@@ -281,11 +291,11 @@ public class BrightnessMappingStrategyTest {
@Test
public void testPhysicalStrategyRecalculateSplines() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
.build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -326,12 +336,12 @@ public class BrightnessMappingStrategyTest {
@Test
public void testDefaultStrategyIsPhysical() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
.build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
}
@@ -342,19 +352,19 @@ public class BrightnessMappingStrategyTest {
float tmp = lux[idx];
lux[idx] = lux[idx + 1];
lux[idx + 1] = tmp;
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// And make sure we get the same result even if it's monotone but not increasing.
lux[idx] = lux[idx + 1];
ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
}
@@ -365,74 +375,74 @@ public class BrightnessMappingStrategyTest {
// Make sure it's strictly increasing so that the only failure is the differing array
// lengths
lux[lux.length - 1] = lux[lux.length - 2] + 1;
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(strategy);
ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
.setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// Extra backlight level
final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
- res = createResources();
+ setUpResources();
ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
// Extra nits level
final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
nits[nits.length - 1] = nits[nits.length - 2] + 1;
- res = createResources();
+ setUpResources();
ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
.setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
- strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
- mMockDwbc);
+ strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+ /* displayWhiteBalanceController= */ null);
assertNull(strategy);
}
@Test
public void testPhysicalStrategyRequiresNitsMapping() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
- BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertNull(physical);
}
@Test
public void testStrategiesAdaptToUserDataPoint() {
- Resources res = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
.setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
.setAutoBrightnessLevels(DISPLAY_LEVELS).build();
- res = createResources();
- assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+ setUpResources();
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
}
@Test
public void testIdleModeConfigLoadsCorrectly() {
- Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
.build();
// Create an idle mode bms
// This will fail if it tries to fetch the wrong configuration.
- BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc,
+ BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(mContext, ddc,
AUTO_BRIGHTNESS_MODE_IDLE,
- mMockDwbc);
+ /* displayWhiteBalanceController= */ null);
assertNotNull("BrightnessMappingStrategy should not be null", bms);
// Ensure that the config is the one we set
@@ -500,36 +510,31 @@ public class BrightnessMappingStrategyTest {
assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
}
- private Resources createResources() {
- return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+ private void setUpResources() {
+ setUpResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
}
- private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-
- Resources mockResources = mock(Resources.class);
+ private void setUpResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
if (luxLevelsIdle.length > 0) {
int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
luxLevelsIdle.length);
- when(mockResources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevelsIdle))
- .thenReturn(luxLevelsIdleResource);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_autoBrightnessLevelsIdle,
+ luxLevelsIdleResource);
}
TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
- when(mockResources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
- .thenReturn(mockBrightnessLevelNitsIdle);
-
- when(mockResources.getInteger(
- com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
- .thenReturn(1);
- when(mockResources.getInteger(
- com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
- .thenReturn(255);
- when(mockResources.getFraction(
- com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
- .thenReturn(MAXIMUM_GAMMA);
- return mockResources;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle,
+ mockBrightnessLevelNitsIdle);
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.integer.config_screenBrightnessSettingMinimum, 1);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.integer.config_screenBrightnessSettingMaximum, 255);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
+ MAXIMUM_GAMMA);
}
private TypedArray createFloatTypedArray(float[] vals) {
@@ -570,11 +575,11 @@ public class BrightnessMappingStrategyTest {
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
.setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Let's start with a validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -600,11 +605,11 @@ public class BrightnessMappingStrategyTest {
final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
- Resources resources = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
.setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Validity check:
assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -627,11 +632,11 @@ public class BrightnessMappingStrategyTest {
public void testGammaCorrectionExtremeChangeAtCenter() {
// Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
// just make sure the adjustment reflects the change.
- Resources resources = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
.setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f);
assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -650,11 +655,11 @@ public class BrightnessMappingStrategyTest {
final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
- Resources resources = createResources();
+ setUpResources();
DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
.setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
- AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
// Validity, as per tradition:
assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -679,15 +684,33 @@ public class BrightnessMappingStrategyTest {
@Test
public void testGetMode() {
- Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+ setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
.build();
- BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
- AUTO_BRIGHTNESS_MODE_IDLE,
- mMockDwbc);
+ BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+ AUTO_BRIGHTNESS_MODE_IDLE, /* displayWhiteBalanceController= */ null);
assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
}
+ @Test
+ public void testAutoBrightnessModeAndPreset() {
+ int mode = AUTO_BRIGHTNESS_MODE_DOZE;
+ int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset);
+
+ setUpResources();
+ DisplayDeviceConfig ddc = new DdcBuilder()
+ .setAutoBrightnessLevels(mode, preset, DISPLAY_LEVELS)
+ .setAutoBrightnessLevelsLux(mode, preset, LUX_LEVELS).build();
+ BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, mode,
+ /* displayWhiteBalanceController= */ null);
+ assertNotNull("BrightnessMappingStrategy should not be null", simple);
+ for (int i = 0; i < LUX_LEVELS.length; i++) {
+ assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
+ }
+ }
+
private static class DdcBuilder {
private DisplayDeviceConfig mDdc;
@@ -695,9 +718,12 @@ public class BrightnessMappingStrategyTest {
mDdc = mock(DisplayDeviceConfig.class);
when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
- when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(LUX_LEVELS);
when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
- when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
+ when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+ .thenReturn(EMPTY_FLOAT_ARRAY);
}
DdcBuilder setNitsRange(float[] nitsArray) {
@@ -711,7 +737,15 @@ public class BrightnessMappingStrategyTest {
}
DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
- when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevels);
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(luxLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevelsLux(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+ float[] luxLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevelsLux(mode, preset)).thenReturn(luxLevels);
return this;
}
@@ -721,7 +755,17 @@ public class BrightnessMappingStrategyTest {
}
DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
- when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
+ when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+ .thenReturn(brightnessLevels);
+ return this;
+ }
+
+ DdcBuilder setAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+ float[] brightnessLevels) {
+ when(mDdc.getAutoBrightnessBrighteningLevels(mode, preset))
+ .thenReturn(brightnessLevels);
return this;
}
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 a4c15b5c5725..7a84406f1b08 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -18,6 +18,8 @@ package com.android.server.display;
import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -40,6 +42,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.os.Temperature;
+import android.provider.Settings;
import android.util.SparseArray;
import android.util.Spline;
import android.view.SurfaceControl;
@@ -605,13 +608,15 @@ public final class DisplayDeviceConfigTest {
private void verifyConfigValuesFromConfigResource() {
assertNull(mDisplayDeviceConfig.getName());
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
- float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
- float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
- assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), new
- float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
- brightnessIntToFloat(150)}, SMALL_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(),
+ new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+ new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+ new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+ brightnessIntToFloat(150)}, SMALL_DELTA);
// Test thresholds
assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -737,30 +742,40 @@ public final class DisplayDeviceConfigTest {
getValidProxSensor(), /* includeIdleMode= */ false));
assertArrayEquals(new float[]{0.0f, 80},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
assertArrayEquals(new float[]{0.2f, 0.3f},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
assertArrayEquals(new float[]{0.0f, 90},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"),
- ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
assertArrayEquals(new float[]{0.3f, 0.4f},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"),
- SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
assertArrayEquals(new float[]{0.0f, 95},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"),
- ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
assertArrayEquals(new float[]{0.35f, 0.45f},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"),
- SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
assertArrayEquals(new float[]{0.0f, 100},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"),
- ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
assertArrayEquals(new float[]{0.4f, 0.5f},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"),
- SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DOZE,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
}
@Test
@@ -772,9 +787,13 @@ public final class DisplayDeviceConfigTest {
assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
assertArrayEquals(new float[]{0, 110, 500},
- mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+ mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+ AUTO_BRIGHTNESS_MODE_DEFAULT,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
assertArrayEquals(new float[]{2, 200, 600},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 02bd35a5b17f..4cc68cf11e69 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -18,6 +18,8 @@ package com.android.server.display;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
import static org.junit.Assert.assertNotNull;
@@ -1568,6 +1570,56 @@ public final class DisplayPowerController2Test {
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
+ @Test
+ public void testSwitchToDozeAutoBrightnessMode() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
+ verify(mHolder.automaticBrightnessController, times(2))
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+ // Back to default mode
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+ }
+
+ @Test
+ public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+ when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never())
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ }
+
+ @Test
+ public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never())
+ .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1853,7 +1905,7 @@ public final class DisplayPowerController2Test {
}
@Override
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 64cdac464720..943862f918bc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1669,7 +1669,7 @@ public final class DisplayPowerControllerTest {
}
@Override
- BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+ BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
DisplayDeviceConfig displayDeviceConfig,
DisplayWhiteBalanceController displayWhiteBalanceController) {
return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
index 49fa2545e001..dafbbb3e0140 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
@@ -23,8 +23,10 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+import java.util.concurrent.Executor
@SmallTest
class DisplayPowerStateTest {
@@ -36,15 +38,21 @@ class DisplayPowerStateTest {
private val mockBlanker = mock<DisplayBlanker>()
private val mockColorFade = mock<ColorFade>()
+ private val mockExecutor = mock<Executor>()
@Before
fun setUp() {
- displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON)
+ displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON,
+ mockExecutor)
}
@Test
fun `destroys ColorFade on stop`() {
displayPowerState.stop()
+ val runnableCaptor = argumentCaptor<Runnable>()
+
+ verify(mockExecutor).execute(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
verify(mockColorFade).destroy()
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 8d8274c61b20..87fc7a484c5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -122,6 +122,16 @@ public class HdrClamperTest {
}
@Test
+ public void testRegisterHdrListener_ZeroMinHdrPercent() {
+ IBinder otherBinder = mock(IBinder.class);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT,
+ /* minimumHdrPercentOfScreen= */ 0, otherBinder);
+
+ verify(mMockHdrInfoListener).unregister(mMockBinder);
+ verify(mMockHdrInfoListener).register(otherBinder);
+ }
+
+ @Test
public void testRegisterNotCalledIfHbmConfigIsMissing() {
IBinder otherBinder = mock(IBinder.class);
mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 3b83e3cc0817..fc2e5b0981e4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -40,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -109,6 +110,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@@ -126,6 +128,8 @@ public class GameManagerServiceTests {
private MockitoSession mMockingSession;
private String mPackageName;
+ private Map<String, Integer> mPackageCategories;
+ private Map<String, Integer> mPackageUids;
private TestLooper mTestLooper;
@Mock
private PackageManager mMockPackageManager;
@@ -229,34 +233,50 @@ public class GameManagerServiceTests {
.strictness(Strictness.WARN)
.startMocking();
mMockContext = new MockContext(InstrumentationRegistry.getContext());
+ mPackageCategories = new HashMap<>();
+ mPackageUids = new HashMap<>();
mPackageName = mMockContext.getPackageName();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
- final Resources resources =
- InstrumentationRegistry.getInstrumentation().getContext().getResources();
- when(mMockPackageManager.getResourcesForApplication(anyString()))
- .thenReturn(resources);
- when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE);
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
}
- private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+ private void mockAppCategory(String packageName, int packageUid,
+ @ApplicationInfo.Category int category)
throws Exception {
reset(mMockPackageManager);
- final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
- gameApplicationInfo.category = category;
- gameApplicationInfo.packageName = packageName;
- final PackageInfo pi = new PackageInfo();
- pi.packageName = packageName;
- pi.applicationInfo = gameApplicationInfo;
- final List<PackageInfo> packages = new ArrayList<>();
- packages.add(pi);
+ mPackageCategories.put(packageName, category);
+ mPackageUids.put(packageName, packageUid);
+ final List<PackageInfo> packageInfos = new ArrayList<>();
+ for (Map.Entry<String, Integer> entry : mPackageCategories.entrySet()) {
+
+ packageName = entry.getKey();
+ packageUid = mPackageUids.get(packageName);
+ category = entry.getValue();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ applicationInfo.category = category;
+ when(mMockPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = applicationInfo;
+ packageInfos.add(pi);
+
+ when(mMockPackageManager.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(
+ packageUid);
+ when(mMockPackageManager.getPackagesForUid(packageUid)).thenReturn(
+ new String[]{packageName});
+ }
when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
- .thenReturn(packages);
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(gameApplicationInfo);
+ .thenReturn(packageInfos);
+ final Resources resources =
+ InstrumentationRegistry.getInstrumentation().getContext().getResources();
+ when(mMockPackageManager.getResourcesForApplication(anyString()))
+ .thenReturn(resources);
}
@After
@@ -508,7 +528,7 @@ public class GameManagerServiceTests {
@Test
public void testGetGameMode_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
mockModifyGameModeGranted();
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -780,7 +800,7 @@ public class GameManagerServiceTests {
@Test
public void testDeviceConfig_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigAll();
mockModifyGameModeGranted();
checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
@@ -1588,7 +1608,7 @@ public class GameManagerServiceTests {
@Test
public void testSetGameState_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
mockDeviceConfigNone();
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
@@ -1611,14 +1631,14 @@ public class GameManagerServiceTests {
gameManagerService.addGameStateListener(mockListener);
verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_AUDIO);
GameState gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
mTestLooper.dispatchAll();
verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1654,7 +1674,7 @@ public class GameManagerServiceTests {
gameManagerService.addGameStateListener(mockListener);
gameManagerService.removeGameStateListener(mockListener);
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_GAME);
GameState gameState = new GameState(false, GameState.MODE_CONTENT);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
@@ -1670,13 +1690,17 @@ public class GameManagerServiceTests {
return output;
}
- private void mockInterventionListForMultipleUsers() {
+ private void mockInterventionListForMultipleUsers() throws Exception {
final String[] packageNames = new String[]{"com.android.app0",
"com.android.app1", "com.android.app2"};
+ int i = 1;
+ for (String p : packageNames) {
+ mockAppCategory(p, DEFAULT_PACKAGE_UID + i++, ApplicationInfo.CATEGORY_GAME);
+ }
final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
final PackageInfo[] pis = new PackageInfo[3];
- for (int i = 0; i < 3; ++i) {
+ for (i = 0; i < 3; ++i) {
applicationInfos[i] = new ApplicationInfo();
applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
applicationInfos[i].packageName = packageNames[i];
@@ -1717,7 +1741,6 @@ public class GameManagerServiceTests {
new Injector());
startUser(gameManagerService, USER_ID_1);
startUser(gameManagerService, USER_ID_2);
-
gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
@@ -1953,7 +1976,7 @@ public class GameManagerServiceTests {
@Test
public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.updateCustomGameModeConfiguration(mPackageName,
@@ -2013,13 +2036,14 @@ public class GameManagerServiceTests {
}
@Test
- public void testWritingSettingFile_onShutdown() throws InterruptedException {
+ public void testWritingSettingFile_onShutdown() throws Exception {
mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onBootCompleted();
startUser(gameManagerService, USER_ID_1);
Thread.sleep(500);
+ mockAppCategory("com.android.app1", DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
GameManager.GAME_MODE_BATTERY, "60", "0.5");
gameManagerService.setGameMode("com.android.app1", USER_ID_1,
@@ -2259,7 +2283,7 @@ public class GameManagerServiceTests {
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
mockModifyGameModeGranted();
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockAppCategory(mPackageName, DEFAULT_PACKAGE_UID, ApplicationInfo.CATEGORY_IMAGE);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
@@ -2277,9 +2301,7 @@ public class GameManagerServiceTests {
mockModifyGameModeGranted();
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
String someGamePkg = "some.game";
- mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
- when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
- DEFAULT_PACKAGE_UID + 1);
+ mockAppCategory(someGamePkg, DEFAULT_PACKAGE_UID + 1, ApplicationInfo.CATEGORY_GAME);
gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
assertEquals(GameManager.GAME_MODE_PERFORMANCE,
gameManagerService.getGameMode(someGamePkg, USER_ID_1));
@@ -2307,24 +2329,11 @@ public class GameManagerServiceTests {
}
@Test
- public void testGamePowerMode_gamePackage() throws Exception {
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
- }
-
- @Test
public void testGamePowerMode_twoGames() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
HashMap<Integer, Boolean> powerState = new HashMap<>();
doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
.when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
@@ -2333,6 +2342,7 @@ public class GameManagerServiceTests {
assertTrue(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ assertFalse(powerState.get(Mode.GAME));
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
assertTrue(powerState.get(Mode.GAME));
@@ -2344,12 +2354,9 @@ public class GameManagerServiceTests {
@Test
public void testGamePowerMode_twoGamesOverlap() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages1 = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
String someGamePkg = "some.game";
- String[] packages2 = {someGamePkg};
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(someGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
@@ -2363,49 +2370,162 @@ public class GameManagerServiceTests {
}
@Test
- public void testGamePowerMode_released() throws Exception {
+ public void testGamePowerMode_noPackage() throws Exception {
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {mPackageName};
+ String[] packages = {};
when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
- gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
}
@Test
- public void testGamePowerMode_noPackage() throws Exception {
+ public void testGamePowerMode_gameAndNotGameApps_flagOn() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String nonGamePkg2 = "not.game2";
+ int nonGameUid2 = DEFAULT_PACKAGE_UID + 2;
+ mockAppCategory(nonGamePkg2, nonGameUid2, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ String gamePkg2 = "game2";
+ int gameUid2 = DEFAULT_PACKAGE_UID + 4;
+ mockAppCategory(gamePkg2, gameUid2, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
- }
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
- @Test
- public void testGamePowerMode_notAGamePackage() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
- GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+ // game1 top to enable game mode
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in foreground with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game2 in background to resume game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 1 in background with no-op
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game 2 in background to stop game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid2, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
- public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
- mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ public void testGamePowerMode_gameAndNotGameApps_flagOff() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DISABLE_GAME_MODE_WHEN_APP_TOP);
GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
- String[] packages = {"someapp"};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+
+ String nonGamePkg1 = "not.game1";
+ int nonGameUid1 = DEFAULT_PACKAGE_UID + 1;
+ mockAppCategory(nonGamePkg1, nonGameUid1, ApplicationInfo.CATEGORY_IMAGE);
+
+ String gamePkg1 = "game1";
+ int gameUid1 = DEFAULT_PACKAGE_UID + 3;
+ mockAppCategory(gamePkg1, gameUid1, ApplicationInfo.CATEGORY_GAME);
+
+ // non-game1 top and background with no-op
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
- DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // game1 top to enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game1 in foreground to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // non-game 1 in background to not interfere
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // move non-game1 to foreground again
+ gameManagerService.mUidObserver.onUidStateChanged(
+ nonGameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+
+ // with non-game1 on top, game 1 in background to still disable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
+
+ // with non-game1 on top, game 1 in foreground to still enable game mode
+ gameManagerService.mUidObserver.onUidStateChanged(
+ gameUid1, ActivityManager.PROCESS_STATE_TOP, 0, 0);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME, false);
+ clearInvocations(mMockPowerManager);
}
@Test
@@ -2432,8 +2552,6 @@ public class GameManagerServiceTests {
gameManagerService.onBootCompleted();
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2448,10 +2566,8 @@ public class GameManagerServiceTests {
// Adding another game to the foreground.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2484,8 +2600,6 @@ public class GameManagerServiceTests {
}));
// Set up a game in the foreground.
- String[] packages = {mPackageName};
- when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
gameManagerService.mUidObserver.onUidStateChanged(
DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
@@ -2503,10 +2617,8 @@ public class GameManagerServiceTests {
// Toggle game default frame rate off.
String anotherGamePkg = "another.game";
- String[] packages2 = {anotherGamePkg};
- mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME);
int somePackageId = DEFAULT_PACKAGE_UID + 1;
- when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+ mockAppCategory(anotherGamePkg, somePackageId, ApplicationInfo.CATEGORY_GAME);
gameManagerService.mUidObserver.onUidStateChanged(
somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0);
gameManagerService.mUidObserver.onUidStateChanged(
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index d24500d180f2..650c473533ed 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -28,8 +28,6 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
-import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
@@ -214,7 +212,6 @@ public class FlexibilityControllerTest {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
- js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
js.setNumAppliedFlexibleConstraints(Integer.bitCount(
mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
@@ -850,75 +847,14 @@ public class FlexibilityControllerTest {
}
@Test
- public void testAllowlistedAppBypass() {
- JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
- JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
- JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_LOW));
- JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_MIN));
- jsHigh.setStandbyBucket(EXEMPTED_INDEX);
- jsDefault.setStandbyBucket(EXEMPTED_INDEX);
- jsLow.setStandbyBucket(EXEMPTED_INDEX);
- jsMin.setStandbyBucket(EXEMPTED_INDEX);
-
- synchronized (mFlexibilityController.mLock) {
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
- }
- }
-
- @Test
- public void testForegroundAppBypass() {
- JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
- JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
- JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_LOW));
- JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
- createJob(0).setPriority(JobInfo.PRIORITY_MIN));
-
- when(mJobSchedulerService.getUidBias(mSourceUid)).thenReturn(JobInfo.BIAS_DEFAULT);
- synchronized (mFlexibilityController.mLock) {
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
- }
-
- when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
- synchronized (mFlexibilityController.mLock) {
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
- }
-
- when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
- synchronized (mFlexibilityController.mLock) {
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
- assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
- assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
- }
- }
-
- @Test
public void testTopAppBypass() {
- JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN);
+ JobInfo.Builder jb = createJob(0);
JobStatus js = createJobStatus("testTopAppBypass", jb);
mJobStore.add(js);
// Needed because if before and after Uid bias is the same, nothing happens.
when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_DEFAULT);
+ .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -929,7 +865,7 @@ public class FlexibilityControllerTest {
assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
- setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION);
+ setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003dcda18..32878b3e199f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@ import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@ import android.os.PackageTagsList;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -97,6 +100,7 @@ import com.android.server.location.injector.TestInjector;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -140,6 +144,9 @@ public class LocationProviderManagerTest {
private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private static final String MISSING_PERMISSION = "missing_permission";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Random mRandom;
@Mock
@@ -1347,6 +1354,24 @@ public class LocationProviderManagerTest {
assertThat(mManager.isVisibleToCaller()).isFalse();
}
+ @Test
+ public void testValidateLocation_futureLocation() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+ Location location = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(location);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+
+ Location futureLocation = createLocation(NAME, mRandom);
+ futureLocation.setElapsedRealtimeNanos(
+ SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+ mProvider.setProviderLocation(futureLocation);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index c2b52b4ee9c8..57326b2e1e82 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -50,9 +50,10 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.pkg.AndroidPackage;
import org.junit.Before;
@@ -175,7 +176,7 @@ public class ApexManagerTest {
mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false);
// isUpdatedSystemApp is ignoreable above, only used for shared library adjustment
return parsedPackage.hideAsFinal();
- } catch (PackageManagerException e) {
+ } catch (PackageParserException e) {
throw new RuntimeException(e);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 7b29e2a4159d..538c0ee52424 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -56,6 +56,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spy
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
import com.android.internal.R
+import com.android.internal.pm.parsing.PackageParser2
import com.android.internal.pm.parsing.pkg.PackageImpl
import com.android.internal.pm.parsing.pkg.ParsedPackage
import com.android.internal.pm.pkg.parsing.ParsingPackage
@@ -69,7 +70,6 @@ import com.android.server.compat.PlatformCompat
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.pm.dex.DexManager
import com.android.server.pm.dex.DynamicCodeLogger
-import com.android.server.pm.parsing.PackageParser2
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.resolution.ComponentResolver
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index da929af3267b..7feafef4e489 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -21,6 +21,7 @@ import android.content.pm.PackageManager
import android.os.Build
import android.os.Process
import android.util.Log
+import com.android.internal.pm.parsing.PackageParserException
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.testutils.whenever
import java.io.File
@@ -120,7 +121,7 @@ class PackageManagerServiceBootTest {
argThat { path: File -> path.path.contains("a.data.package") },
anyInt(),
anyBoolean()))
- .thenThrow(PackageManagerException(
+ .thenThrow(PackageParserException(
PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
val pm = createPackageManagerService()
verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw(
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 2d36ff311d4f..d49bc4326a5a 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -7,3 +7,4 @@ per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
+per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
index 6fca56134393..69817ad5035a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeConfigurationsTest.java
@@ -16,17 +16,21 @@
package com.android.server.audio;
-import static android.media.AudioAttributes.USAGE_MEDIA;
-import static android.media.AudioAttributes.USAGE_GAME;
-import static android.media.AudioAttributes.USAGE_ASSISTANT;
import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_ASSISTANT;
+import static android.media.AudioAttributes.USAGE_EMERGENCY;
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.AudioAttributes.USAGE_MEDIA;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
+import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.media.AudioAttributes;
+import android.media.FadeManagerConfiguration;
import android.media.VolumeShaper;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.google.common.truth.Expect;
@@ -42,6 +46,7 @@ import java.util.List;
public final class FadeConfigurationsTest {
private FadeConfigurations mFadeConfigurations;
private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+ private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final long DURATION_FOR_UNFADEABLE_MS = 0;
private static final int TEST_UID_SYSTEM = 1000;
@@ -60,11 +65,19 @@ public final class FadeConfigurationsTest {
private static final VolumeShaper.Configuration DEFAULT_FADEOUT_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
- .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f} ,
- /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+ .setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
+ /* volumes= */ new float[]{1.f, 0.65f, 0.0f})
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
.setDuration(DEFAULT_FADE_OUT_DURATION_MS)
.build();
+ private static final VolumeShaper.Configuration DEFAULT_FADEIN_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(PlaybackActivityMonitor.VOLUME_SHAPER_SYSTEM_FADEOUT_ID)
+ .setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
+ /* volumes= */ new float[]{0.f, 0.30f, 1.0f})
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+ .build();
private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build();
@@ -72,12 +85,18 @@ public final class FadeConfigurationsTest {
new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setUsage(USAGE_ASSISTANT).build();
+ private static final AudioAttributes TEST_EMERGENCY_AUDIO_ATTRIBUTE =
+ new AudioAttributes.Builder().setSystemUsage(USAGE_EMERGENCY).build();
+
private static final AudioAttributes TEST_SPEECH_AUDIO_ATTRIBUTE =
new AudioAttributes.Builder().setContentType(CONTENT_TYPE_SPEECH).build();
@Rule
public final Expect expect = Expect.create();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
mFadeConfigurations = new FadeConfigurations();
@@ -156,4 +175,110 @@ public final class FadeConfigurationsTest {
.that(mFadeConfigurations.isFadeable(TEST_GAME_AUDIO_ATTRIBUTE, TEST_UID_USER,
PLAYER_TYPE_AAUDIO)).isFalse();
}
+
+ @Test
+ public void testGetFadeableUsages_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> usageList = List.of(AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_EMERGENCY);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ usageList,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Fadeable usages with fade manager configuration")
+ .that(fadeConfigs.getFadeableUsages()).isEqualTo(fmc.getFadeableUsages());
+ }
+
+ @Test
+ public void testGetUnfadeableContentTypes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> contentTypesList = List.of(AudioAttributes.CONTENT_TYPE_MUSIC,
+ AudioAttributes.CONTENT_TYPE_MOVIE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ contentTypesList, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable content types with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableContentTypes())
+ .isEqualTo(fmc.getUnfadeableContentTypes());
+ }
+
+ @Test
+ public void testGetUnfadeableAudioAttributes_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<AudioAttributes> attrsList = List.of(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+ TEST_EMERGENCY_AUDIO_ATTRIBUTE);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ null,
+ /* unfadeableAudioAttrs= */ attrsList);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable audio attributes with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableAudioAttributes())
+ .isEqualTo(fmc.getUnfadeableAudioAttributes());
+ }
+
+ @Test
+ public void testGetUnfadeableUids_withFadeManagerConfigurations_equals() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION);
+ List<Integer> uidsList = List.of(TEST_UID_SYSTEM, TEST_UID_USER);
+ FadeManagerConfiguration fmc = createFadeMgrConfig(/* fadeableUsages= */ null,
+ /* unfadeableContentTypes= */ null, /* unfadeableUids= */ uidsList,
+ /* unfadeableAudioAttrs= */ null);
+ FadeConfigurations fadeConfigs = new FadeConfigurations();
+
+ fadeConfigs.setFadeManagerConfiguration(fmc);
+
+ expect.withMessage("Unfadeable uids with fade manager configuration")
+ .that(fadeConfigs.getUnfadeableUids()).isEqualTo(fmc.getUnfadeableUids());
+ }
+
+ private static FadeManagerConfiguration createFadeMgrConfig(List<Integer> fadeableUsages,
+ List<Integer> unfadeableContentTypes, List<Integer> unfadeableUids,
+ List<AudioAttributes> unfadeableAudioAttrs) {
+ FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder();
+ if (fadeableUsages != null) {
+ builder.setFadeableUsages(fadeableUsages);
+ }
+ if (unfadeableContentTypes != null) {
+ builder.setUnfadeableContentTypes(unfadeableContentTypes);
+ }
+ if (unfadeableUids != null) {
+ builder.setUnfadeableUids(unfadeableUids);
+ }
+ if (unfadeableAudioAttrs != null) {
+ builder.setUnfadeableAudioAttributes(unfadeableAudioAttrs);
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeOutVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEOUT_VSHAPE);
+ }
+ }
+ if (fadeableUsages != null) {
+ for (int index = 0; index < fadeableUsages.size(); index++) {
+ builder.setFadeInVolumeShaperConfigForAudioAttributes(
+ createGenericAudioAttributesForUsage(fadeableUsages.get(index)),
+ DEFAULT_FADEIN_VSHAPE);
+ }
+ }
+
+ return builder.build();
+ }
+
+ private static AudioAttributes createGenericAudioAttributesForUsage(int usage) {
+ if (AudioAttributes.isSystemUsage(usage)) {
+ return new AudioAttributes.Builder().setSystemUsage(usage).build();
+ }
+ return new AudioAttributes.Builder().setUsage(usage).build();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
index 65059d5ca8fd..fa94821d4ff2 100644
--- a/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/FadeOutManagerTest.java
@@ -23,8 +23,6 @@ import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK;
import static android.media.AudioPlaybackConfiguration.PLAYER_TYPE_UNKNOWN;
-import static org.junit.Assert.assertThrows;
-
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -75,20 +73,11 @@ public final class FadeOutManagerTest {
@Before
public void setUp() {
- mFadeOutManager = new FadeOutManager(new FadeConfigurations());
+ mFadeOutManager = new FadeOutManager();
mContext = ApplicationProvider.getApplicationContext();
}
@Test
- public void constructor_nullFadeConfigurations_fails() {
- Throwable thrown = assertThrows(NullPointerException.class, () -> new FadeOutManager(
- /* FadeConfigurations= */ null));
-
- expect.withMessage("Constructor exception")
- .that(thrown).hasMessageThat().contains("Fade configurations can not be null");
- }
-
- @Test
public void testCanCauseFadeOut_forFaders_returnsTrue() {
FocusRequester winner = createFocusRequester(TEST_MEDIA_AUDIO_ATTRIBUTE, "winning-client",
"unit-test", TEST_UID_USER,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 94cb860ae710..74f8f086803f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -29,7 +29,7 @@ import android.hardware.biometrics.BiometricRequestConstants;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
@@ -43,10 +43,10 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
-@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
@Presubmit
@SmallTest
public class SensorOverlaysTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int SENSOR_ID = 11;
private static final long REQUEST_ID = 8;
@@ -59,6 +59,7 @@ public class SensorOverlaysTest {
@Before
public void setup() {
+ mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
when(mAcquisitionClient.getRequestId()).thenReturn(REQUEST_ID);
when(mAcquisitionClient.hasRequestId()).thenReturn(true);
}
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 c9e1c4a8bfc5..3aaac2e9cf1b 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
@@ -16,21 +16,30 @@
package com.android.server.biometrics.sensors.face;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -42,6 +51,7 @@ import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.biometrics.Flags;
@@ -66,6 +76,7 @@ public class FaceServiceTest {
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
+ private static final String OP_PACKAGE_NAME = "FaceServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -78,15 +89,19 @@ public class FaceServiceTest {
@Rule
public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Mock
- FaceProvider mFaceProviderDefault;
+ private FaceProvider mFaceProviderDefault;
+ @Mock
+ private FaceProvider mFaceProviderVirtual;
+ @Mock
+ private IFace mDefaultFaceDaemon;
@Mock
- FaceProvider mFaceProviderVirtual;
+ private IFace mVirtualFaceDaemon;
@Mock
- IFace mDefaultFaceDaemon;
+ private IBiometricService mIBiometricService;
@Mock
- IFace mVirtualFaceDaemon;
+ private IBinder mToken;
@Mock
- IBiometricService mIBiometricService;
+ private IFaceServiceReceiver mFaceServiceReceiver;
private final SensorProps mDefaultSensorProps = new SensorProps();
private final SensorProps mVirtualSensorProps = new SensorProps();
@@ -117,7 +132,13 @@ public class FaceServiceTest {
new SensorProps[]{mVirtualSensorProps});
when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+ when(mFaceProviderDefault.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+ when(mFaceProviderVirtual.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ mContext.getTestablePermissions().setPermission(
+ USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
(name) -> {
@@ -136,7 +157,13 @@ public class FaceServiceTest {
if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
return null;
- }, () -> mIBiometricService);
+ }, () -> mIBiometricService,
+ (name) -> {
+ if (NAME_DEFAULT.equals(name)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(name)) return mFaceProviderVirtual;
+ return null;
+ },
+ () -> new String[]{NAME_DEFAULT, NAME_VIRTUAL});
}
@Test
@@ -191,6 +218,39 @@ public class FaceServiceTest {
eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
}
+ @Test
+ public void testOptionsForAuthentication() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .build();
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+
+ final long operationId = 5;
+ mFaceService.mServiceWrapper.authenticate(mToken, operationId, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
+ .getPackageName())
+ .build();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+ mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
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 f570ba23441d..88956b614eae 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
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -61,6 +62,7 @@ import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -92,6 +94,7 @@ public class FingerprintServiceTest {
private static final String NAME_VIRTUAL = "virtual";
private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
List.of();
+ private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -343,6 +346,24 @@ public class FingerprintServiceTest {
assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
}
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+ new FingerprintAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(
+ OP_PACKAGE_NAME).getPackageName())
+ .build();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initServiceWithAndWait(NAME_DEFAULT);
+ mService.mServiceWrapper.detectFingerprint(mToken, mServiceReceiver,
+ fingerprintAuthenticateOptions);
+
+ assertThat(fingerprintAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
FingerprintProvider provider, long operationId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c24227fcd1f7..774ea5bc6b16 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -161,6 +161,7 @@ public class FingerprintAuthenticationClientTest {
@Before
public void setup() {
+ mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
mContext.addMockSystemService(BiometricManager.class, mBiometricManager);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i ->
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 071d571adabe..9b28b817a1b9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -53,11 +53,11 @@ import java.util.List;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualCameraControllerTest {
- private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+ private static final String CAMERA_NAME_1 = "Virtual camera 1";
private static final int CAMERA_WIDTH_1 = 100;
private static final int CAMERA_HEIGHT_1 = 200;
- private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+ private static final String CAMERA_NAME_2 = "Virtual camera 2";
private static final int CAMERA_WIDTH_2 = 400;
private static final int CAMERA_HEIGHT_2 = 600;
private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
@@ -84,7 +84,7 @@ public class VirtualCameraControllerTest {
@Test
public void registerCamera_registersCamera() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -98,7 +98,7 @@ public class VirtualCameraControllerTest {
@Test
public void unregisterCamera_unregistersCamera() throws Exception {
VirtualCameraConfig config = createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
mVirtualCameraController.registerCamera(config);
mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +109,9 @@ public class VirtualCameraControllerTest {
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
mVirtualCameraController.close();
@@ -129,10 +129,10 @@ public class VirtualCameraControllerTest {
}
private VirtualCameraConfig createVirtualCameraConfig(
- int width, int height, int format, int displayNameResId) {
+ int width, int height, int format, String displayName) {
return new VirtualCameraConfig.Builder()
.addStreamConfig(width, height, format)
- .setDisplayNameStringRes(displayNameResId)
+ .setName(displayName)
.setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index a63d01b6225a..e7da26ea38b1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -456,6 +456,14 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
}
@Test
+ public void avbEnabled_standby_avbDisabled() {
+ enableAbsoluteVolumeBehavior();
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
+
+ @Test
public void avbEnabled_cecVolumeDisabled_avbDisabled() {
enableAbsoluteVolumeBehavior();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index ce15c6d30531..eb9cce007b77 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,10 +67,10 @@ import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.compat.PlatformCompat;
import com.android.server.integrity.engine.RuleEvaluationEngine;
import com.android.server.integrity.model.IntegrityCheckResult;
-import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.testutils.TestUtils;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 1bfd43ff60ef..25eedf5652d7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
+import com.android.internal.pm.parsing.PackageParserException;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.PackageManagerException;
import com.android.server.pm.parsing.TestPackageParser2;
@@ -161,7 +162,8 @@ public class DexMetadataHelperTest {
}
@Test
- public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException {
+ public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException,
+ PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk");
ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
@@ -178,7 +180,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageSplitsWithDmFileValid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageManagerException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
@@ -201,7 +203,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageSplitsNoBaseWithDmFileValid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageManagerException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_feature_a.apk");
@@ -219,7 +221,7 @@ public class DexMetadataHelperTest {
}
@Test
- public void testParsePackageWithDmFileInvalid() throws IOException {
+ public void testParsePackageWithDmFileInvalid() throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
Files.createFile(invalidDmFile.toPath());
@@ -242,7 +244,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageSplitsWithDmFileInvalid()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
createDexMetadataFile("install_split_base.apk");
@@ -268,7 +270,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileInvalidManifest()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
@@ -283,7 +285,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileEmptyManifest()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
/*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
@@ -299,7 +301,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileBadPackageName()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -315,7 +317,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileBadVersionCode()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
/*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -331,7 +333,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileMissingPackageName()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -347,7 +349,7 @@ public class DexMetadataHelperTest {
@Test
public void testParsePackageWithDmFileMissingVersionCode()
- throws IOException, PackageManagerException {
+ throws IOException, PackageParserException {
copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
/*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 9b4ca2a86f2c..6a088d99588e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -36,7 +36,7 @@ import org.xmlpull.v1.XmlPullParserFactory
@Postsubmit
class AndroidPackageParsingValidationTest {
companion object {
- private val parser2 = PackageParser2.forParsingFileWithDefaults()
+ private val parser2 = PackageParserUtils.forParsingFileWithDefaults()
private val apks = ((PackageManagerService.SYSTEM_PARTITIONS)
.flatMap {
listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder)
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
index c44f583a93ef..e420e4b52b6e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -18,11 +18,12 @@ package com.android.server.pm.parsing
import android.content.pm.ApplicationInfo
import android.util.ArraySet
+import com.android.internal.pm.parsing.PackageParser2
import java.io.File
class TestPackageParser2(var cacheDir: File? = null) : PackageParser2(
null /* separateProcesses */, null /* displayMetrics */,
- cacheDir /* cacheDir */, object : PackageParser2.Callback() {
+ cacheDir?.let { PackageCacher(cacheDir) }, object : PackageParser2.Callback() {
override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean {
return true
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 848790381984..89056cc34795 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -64,17 +64,18 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.intThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
@@ -99,7 +100,6 @@ import android.util.Pair;
import android.view.Display;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -110,6 +110,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -125,6 +126,7 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
/**
@@ -175,6 +177,9 @@ public class AppStandbyControllerTests {
private static final int ASSERT_RETRY_ATTEMPTS = 20;
private static final int ASSERT_RETRY_DELAY_MILLISECONDS = 500;
+ @DurationMillisLong
+ private static final long FLUSH_TIMEOUT_MILLISECONDS = 5_000;
+
/** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */
private static boolean isPackageInstalled = true;
@@ -563,6 +568,7 @@ public class AppStandbyControllerTests {
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
setupInitialUsageHistory();
+ flushHandler(mController);
}
@After
@@ -571,12 +577,9 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBoundWidgetPackageExempt() throws Exception {
assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
- assertEquals(STANDBY_BUCKET_ACTIVE,
- mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
- mInjector.mElapsedRealtime, false));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_EXEMPTED_1);
}
@Test
@@ -654,7 +657,6 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Charging() throws Exception {
TestParoleListener paroleListener = new TestParoleListener();
mController.addListener(paroleListener);
@@ -662,7 +664,7 @@ public class AppStandbyControllerTests {
setChargingState(mController, false);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
@@ -671,7 +673,7 @@ public class AppStandbyControllerTests {
setChargingState(mController, true);
paroleListener.awaitOnLatch(2000);
assertTrue(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertTrue(mController.isInParole());
@@ -680,14 +682,13 @@ public class AppStandbyControllerTests {
setChargingState(mController, false);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
assertFalse(mController.isInParole());
}
@Test
- @FlakyTest(bugId = 185169504)
public void testIsAppIdle_Enabled() throws Exception {
setChargingState(mController, false);
TestParoleListener paroleListener = new TestParoleListener();
@@ -696,7 +697,7 @@ public class AppStandbyControllerTests {
setAppIdleEnabled(mController, true);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
@@ -711,7 +712,7 @@ public class AppStandbyControllerTests {
setAppIdleEnabled(mController, true);
paroleListener.awaitOnLatch(2000);
assertFalse(paroleListener.getParoleState());
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
}
@@ -723,6 +724,7 @@ public class AppStandbyControllerTests {
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket,
int userId) {
mInjector.mElapsedRealtime = elapsedTime;
+ flushHandler(controller);
controller.checkIdleStates(userId);
assertEquals(bucket,
controller.getAppStandbyBucket(PACKAGE_1, userId, mInjector.mElapsedRealtime,
@@ -744,50 +746,85 @@ public class AppStandbyControllerTests {
}
private int getStandbyBucket(int userId, AppStandbyController controller, String packageName) {
+ flushHandler(controller);
return controller.getAppStandbyBucket(packageName, userId, mInjector.mElapsedRealtime,
true);
}
+ private List<AppStandbyInfo> getStandbyBuckets(int userId) {
+ flushHandler(mController);
+ return mController.getAppStandbyBuckets(userId);
+ }
+
private int getStandbyBucketReason(String packageName) {
+ flushHandler(mController);
return mController.getAppStandbyBucketReason(packageName, USER_ID,
mInjector.mElapsedRealtime);
}
- private void assertBucket(int bucket) throws InterruptedException {
- assertBucket(bucket, PACKAGE_1);
+ private void waitAndAssertBucket(int bucket, String pkg) {
+ waitAndAssertBucket(mController, bucket, pkg);
}
- private void assertBucket(int bucket, String pkg) throws InterruptedException {
- int retries = 0;
- do {
- if (bucket == getStandbyBucket(mController, pkg)) {
- // Success
- return;
- }
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
- retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertEquals(bucket, getStandbyBucket(mController, pkg));
+ private void waitAndAssertBucket(AppStandbyController controller, int bucket, String pkg) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(pkg);
+ sb.append(" was not in the ");
+ sb.append(UsageStatsManager.standbyBucketToString(bucket));
+ sb.append(" (");
+ sb.append(bucket);
+ sb.append(") bucket.");
+ waitAndAssertBucket(sb.toString(), controller, bucket, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, int bucket, String pkg) {
+ waitAndAssertBucket(msg, mController, bucket, pkg);
}
- private void assertNotBucket(int bucket) throws InterruptedException {
- final String pkg = PACKAGE_1;
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ String pkg) {
+ waitAndAssertBucket(msg, controller, bucket, USER_ID, pkg);
+ }
+
+ private void waitAndAssertBucket(String msg, AppStandbyController controller, int bucket,
+ int userId,
+ String pkg) {
+ waitUntil(() -> bucket == getStandbyBucket(userId, controller, pkg));
+ assertEquals(msg, bucket, getStandbyBucket(userId, controller, pkg));
+ }
+
+ private void waitAndAssertNotBucket(int bucket, String pkg) {
+ waitAndAssertNotBucket(mController, bucket, pkg);
+ }
+
+ private void waitAndAssertNotBucket(AppStandbyController controller, int bucket, String pkg) {
+ waitUntil(() -> bucket != getStandbyBucket(controller, pkg));
+ assertNotEquals(bucket, getStandbyBucket(controller, pkg));
+ }
+
+ private void waitAndAssertLastNoteEvent(int event) {
+ waitUntil(() -> {
+ flushHandler(mController);
+ return event == mInjector.mLastNoteEvent;
+ });
+ assertEquals(event, mInjector.mLastNoteEvent);
+ }
+
+ // Waits until condition is true or times out.
+ private void waitUntil(BooleanSupplier resultSupplier) {
int retries = 0;
do {
- if (bucket != getStandbyBucket(mController, pkg)) {
- // Success
- return;
+ if (resultSupplier.getAsBoolean()) return;
+ try {
+ Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
+ } catch (InterruptedException ie) {
+ // Do nothing
}
- Thread.sleep(ASSERT_RETRY_DELAY_MILLISECONDS);
retries++;
- } while(retries < ASSERT_RETRY_ATTEMPTS);
- // try one last time
- assertNotEquals(bucket, getStandbyBucket(mController, pkg));
+ } while (retries < ASSERT_RETRY_ATTEMPTS);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testBuckets() throws Exception {
assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -820,14 +857,13 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSetAppStandbyBucket() throws Exception {
// For a known package, standby bucket should be set properly
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// For an unknown package, standby bucket should not be set, hence NEVER is returned
// Ensure the unknown package is not already in history by removing it
@@ -836,21 +872,20 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_TIMEOUT);
isPackageInstalled = true; // Reset mocked variable for other tests
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testAppStandbyBucketOnInstallAndUninstall() throws Exception {
// On package install, standby bucket should be ACTIVE
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_UNKNOWN);
// On uninstall, package should not exist in history and should return a NEVER bucket
mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_UNKNOWN);
// Ensure uninstalled app is not in history
- List<AppStandbyInfo> buckets = mController.getAppStandbyBuckets(USER_ID);
+ List<AppStandbyInfo> buckets = getStandbyBuckets(USER_ID);
for(AppStandbyInfo bucket : buckets) {
if (bucket.mPackageName.equals(PACKAGE_UNKNOWN)) {
fail("packageName found in app idle history after uninstall.");
@@ -859,7 +894,6 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testScreenTimeAndBuckets() throws Exception {
mInjector.setDisplayOn(false);
@@ -876,22 +910,21 @@ public class AppStandbyControllerTests {
// RARE bucket, should fail because the screen wasn't ON.
mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
mController.checkIdleStates(USER_ID);
- assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.setDisplayOn(true);
assertTimeout(mController, RARE_THRESHOLD + 2 * HOUR_MS + 1, STANDBY_BUCKET_RARE);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testForcedIdle() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
mController.forceIdleState(PACKAGE_1, USER_ID, false);
- assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
- true));
+
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
}
@@ -901,15 +934,15 @@ public class AppStandbyControllerTests {
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
}
@@ -917,9 +950,10 @@ public class AppStandbyControllerTests {
public void testNotificationEvent_bucketPromotion_changePromotedBucket() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
+ mInjector.mElapsedRealtime += RARE_THRESHOLD + 1;
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// TODO: Avoid hardcoding these string constants.
mInjector.mSettingsBuilder.setInt("notification_seen_promoted_bucket",
@@ -928,11 +962,10 @@ public class AppStandbyControllerTests {
mInjector.getDeviceConfigProperties());
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testNotificationEvent_quotaBump() throws Exception {
mInjector.mSettingsBuilder
.setBoolean("trigger_quota_bump_on_notification_seen", true);
@@ -942,7 +975,7 @@ public class AppStandbyControllerTests {
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = RARE_THRESHOLD * 2;
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
@@ -951,83 +984,80 @@ public class AppStandbyControllerTests {
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertTrue(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedEvent() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 1;
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSlicePinnedPrivEvent() throws Exception {
mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward 12 hours
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should still be in predicted bucket, since prediction timeout is 1 day since prediction
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Fast forward two more hours
mInjector.mElapsedRealtime += 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
// Should have now applied prediction timeout
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Fast forward RARE bucket
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
// Should continue to apply prediction timeout
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testOverrides() throws Exception {
// Can force to NEVER
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't override FORCED reasons
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Prediction can't override NEVER
mInjector.mElapsedRealtime = 2 * HOUR_MS;
@@ -1035,115 +1065,114 @@ public class AppStandbyControllerTests {
REASON_MAIN_DEFAULT);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
// Prediction can't set to NEVER
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Prediction can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Force from user can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Force from system can remove from RESTRICTED if it was put it in due to system
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_PREDICTED);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Non-user usage can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Explicit user usage can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION);
- assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND);
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
// Use recent prediction
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Way past prediction timeout, use system thresholds
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
/** Test that timeouts still work properly even if invalid configuration values are set. */
@Test
- @FlakyTest(bugId = 185169504)
public void testTimeout_InvalidThresholds() throws Exception {
mInjector.mSettingsBuilder
.setLong("screen_threshold_active", -1)
@@ -1161,19 +1190,19 @@ public class AppStandbyControllerTests {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
mInjector.mElapsedRealtime = 2 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime = 4 * HOUR_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1181,74 +1210,72 @@ public class AppStandbyControllerTests {
* timeout has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
+ @Ignore("b/317086276")
public void testTimeoutBeforeRestricted() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += DAY_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Way past all timeouts. Make sure timeout processing doesn't raise bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += mInjector.getAutoRestrictedBucketDelayMs() - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedDelay_DelayChange() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mAutoRestrictedBucketDelayMs = 2 * HOUR_MS;
mInjector.mElapsedRealtime += 2 * HOUR_MS - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1256,36 +1283,35 @@ public class AppStandbyControllerTests {
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to RARE Not long enough to time out into RESTRICTED.
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED. Instead of reverting to
// predicted RARE, should go into RESTRICTED
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Ensure that prediction can still raise it out despite this override.
mInjector.mElapsedRealtime += 1;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
/**
@@ -1293,7 +1319,6 @@ public class AppStandbyControllerTests {
* a low bucket after the RESTRICTED timeout.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1301,7 +1326,7 @@ public class AppStandbyControllerTests {
mInjector.mElapsedRealtime += RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
mInjector.mElapsedRealtime += 1;
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1310,10 +1335,10 @@ public class AppStandbyControllerTests {
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
/**
@@ -1321,261 +1346,250 @@ public class AppStandbyControllerTests {
* interaction.
*/
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionOverridesRestrictedTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Long enough that it could have timed out into RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Report system interaction.
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Ensure that it's raised out of RESTRICTED for the system interaction elevation duration.
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Elevation duration over. Should fall back down.
mInjector.mElapsedRealtime += 10 * MINUTE_MS;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Since the app timed out into RESTRICTED, prediction should be able to remove from the
// bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Prediction into a low bucket means no expectation of the app being used, so we shouldn't
// elevate the app from RESTRICTED.
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testCascadingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testOverlappingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, NOTIFICATION_SEEN, 1000, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Overlapping USER_INTERACTION before previous one times out
reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000,
PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Still in ACTIVE after first USER_INTERACTION times out
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Both timed out, so NOTIFICATION_SEEN timeout should be effective
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemInteractionTimeout() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
// Fast forward to RARE
mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a SYSTEM_INTERACTION and verify bucket
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testInitialForegroundServiceTimeout() throws Exception {
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_FORCED_BY_USER);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_NEVER);
+ waitAndAssertBucket(STANDBY_BUCKET_NEVER, PACKAGE_1);
mInjector.mElapsedRealtime += 100;
// Trigger a FOREGROUND_SERVICE_START and verify bucket
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it's still in ACTIVE close to end of timeout
mInjector.mElapsedRealtime += mController.mInitialForegroundServiceStartTimeoutMillis - 100;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify bucket moves to RARE after timeout
mInjector.mElapsedRealtime += 200;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Trigger a FOREGROUND_SERVICE_START again
reportEvent(mController, FOREGROUND_SERVICE_START, mInjector.mElapsedRealtime, PACKAGE_1);
mController.checkIdleStates(USER_ID);
// Bucket should not be immediately elevated on subsequent service starts
- assertBucket(STANDBY_BUCKET_RARE);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionNotOverridden() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000;
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Falls back to WORKING_SET
mInjector.mElapsedRealtime += 5000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_WORKING_SET);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// Predict to ACTIVE
mInjector.mElapsedRealtime += 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// CheckIdleStates should not change the prediction
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testPredictionStrikesBack() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Predict to FREQUENT
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_PREDICTED);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Add a short timeout event
mInjector.mElapsedRealtime += 1000;
reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += 1000;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_ACTIVE);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
// Verify it reverted to predicted
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_FREQUENT);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_NotAddedForUserForce() throws Exception {
final int expectedReason = REASON_MAIN_FORCED_BY_USER;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_AddedForSystemForce() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1584,13 +1598,13 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1598,7 +1612,6 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1607,14 +1620,14 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
getStandbyBucketReason(PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
- assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
@@ -1623,20 +1636,19 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
- assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
// Flags should be combined
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
getStandbyBucketReason(PACKAGE_1));
mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED);
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Flags should not be combined since the bucket changed.
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
getStandbyBucketReason(PACKAGE_1));
}
@Test
- @FlakyTest(bugId = 185169504)
public void testRestrictApp_MainReason() throws Exception {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_DEFAULT);
@@ -1644,11 +1656,11 @@ public class AppStandbyControllerTests {
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_PREDICTED, 0);
// Call should be ignored.
- assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mController.restrictApp(PACKAGE_1, USER_ID, REASON_MAIN_FORCED_BY_USER, 0);
// Call should go through
- assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1724,15 +1736,15 @@ public class AppStandbyControllerTests {
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUserInteraction_CrossProfile() throws Exception {
mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2);
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("Cross profile connected package bucket should be elevated on usage",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
- assertEquals("Not Cross profile connected package bucket should not be elevated on usage",
- STANDBY_BUCKET_NEVER, getStandbyBucket(USER_ID3, mController, PACKAGE_1));
+ waitAndAssertBucket("Cross profile connected package bucket should be elevated on usage",
+ mController, STANDBY_BUCKET_ACTIVE, USER_ID2, PACKAGE_1);
+ waitAndAssertBucket(
+ "Not Cross profile connected package bucket should not be elevated on usage",
+ mController, STANDBY_BUCKET_NEVER, USER_ID3, PACKAGE_1);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID);
assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID2);
@@ -1742,51 +1754,50 @@ public class AppStandbyControllerTests {
mInjector.mCrossProfileTargets = Collections.emptyList();
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
- assertEquals("No longer cross profile connected package bucket should not be "
- + "elevated on usage",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(USER_ID2, mController, PACKAGE_1));
+ waitAndAssertBucket("No longer cross profile connected package bucket should not be "
+ + "elevated on usage", mController, STANDBY_BUCKET_WORKING_SET, USER_ID2,
+ PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testUnexemptedSyncScheduled() throws Exception {
rearmLatch(PACKAGE_1);
mController.addListener(mListener);
- assertEquals("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Test package did not start in the Never bucket", STANDBY_BUCKET_NEVER,
+ PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should bring the package out of the Never bucket",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket(
+ "Unexempted sync scheduled should bring the package out of the Never bucket",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, false);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Unexempted sync scheduled should not elevate a non Never bucket",
- STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Unexempted sync scheduled should not elevate a non Never bucket",
+ STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
- @FlakyTest(bugId = 185169504)
public void testExemptedSyncScheduled() throws Exception {
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = true;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled in doze should set bucket to working set",
- STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled in doze should set bucket to working set",
+ STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
mInjector.mDeviceIdleMode = false;
rearmLatch(PACKAGE_1);
mController.postReportSyncScheduled(PACKAGE_1, USER_ID, true);
mStateChangedLatch.await(1000, TimeUnit.MILLISECONDS);
- assertEquals("Exempted sync scheduled while not in doze should set bucket to active",
- STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Exempted sync scheduled while not in doze should set bucket to active",
+ STANDBY_BUCKET_ACTIVE, PACKAGE_1);
}
@Test
@@ -1796,14 +1807,14 @@ public class AppStandbyControllerTests {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for a non-buggy
// reason.
@@ -1814,11 +1825,11 @@ public class AppStandbyControllerTests {
| REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates should change bucket if the app was forced by the system for a buggy reason.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1827,11 +1838,11 @@ public class AppStandbyControllerTests {
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertNotEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertNotBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the system for more than just
// a buggy reason.
@@ -1842,13 +1853,13 @@ public class AppStandbyControllerTests {
| REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
getStandbyBucketReason(PACKAGE_1));
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
// Updates shouldn't change bucket if the app was forced by the user.
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -1857,11 +1868,11 @@ public class AppStandbyControllerTests {
REASON_MAIN_FORCED_BY_USER);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp(PACKAGE_1, USER_ID2);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
mController.maybeUnrestrictBuggyApp("com.random.package", USER_ID);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
+ waitAndAssertBucket(STANDBY_BUCKET_RESTRICTED, PACKAGE_1);
}
@Test
@@ -1876,37 +1887,37 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADFULL, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
// Make sure headless system apps don't get lowered.
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADLESS, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
// Package 1 doesn't have activities and is headless, but is not a system app, so it can
// be lowered.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
public void testWellbeingAppElevated() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure the default wellbeing app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING);
// A non default wellbeing app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
}
@Test
@@ -1914,22 +1925,22 @@ public class AppStandbyControllerTests {
mInjector.mClockApps.add(Pair.create(PACKAGE_1, UID_1));
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1);
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_2);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_2);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure a clock app does not get lowered below WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
+ waitAndAssertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_1);
// A non clock app should be able to fall lower than WORKING_SET.
mController.setAppStandbyBucket(PACKAGE_2, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
+ waitAndAssertBucket(STANDBY_BUCKET_RARE, PACKAGE_2);
}
@Test
@@ -2067,13 +2078,13 @@ public class AppStandbyControllerTests {
public void testBackgroundLocationBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime,
PACKAGE_BACKGROUND_LOCATION);
- assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_BACKGROUND_LOCATION);
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
// Make sure PACKAGE_BACKGROUND_LOCATION does not get lowered than STANDBY_BUCKET_FREQUENT.
mController.setAppStandbyBucket(PACKAGE_BACKGROUND_LOCATION, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
- assertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
+ waitAndAssertBucket(STANDBY_BUCKET_FREQUENT, PACKAGE_BACKGROUND_LOCATION);
}
@Test
@@ -2083,41 +2094,41 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
// Since we're staying on the PACKAGE_ACTIVE side, noteEvent shouldn't be called.
// Reset the last event to confirm the method isn't called.
mInjector.mLastNoteEvent = BatteryStats.HistoryItem.EVENT_NONE;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_NONE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_NONE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_EXEMPTED,
REASON_MAIN_FORCED_BY_USER);
- assertEquals(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, mInjector.mLastNoteEvent);
+ waitAndAssertLastNoteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE);
}
private String getAdminAppsStr(int userId) {
@@ -2187,8 +2198,7 @@ public class AppStandbyControllerTests {
rearmLatch(pkg);
mController.setAppStandbyBucket(pkg, user, bucket, reason);
mStateChangedLatch.await(1, TimeUnit.SECONDS);
- assertEquals("Failed to set package bucket", bucket,
- getStandbyBucket(mController, PACKAGE_1));
+ waitAndAssertBucket("Failed to set package bucket", bucket, PACKAGE_1);
}
private void rearmLatch(String pkgName) {
@@ -2205,4 +2215,12 @@ public class AppStandbyControllerTests {
mLatchUserId = userId;
mQuotaBumpLatch = new CountDownLatch(1);
}
+
+ private void flushHandler(AppStandbyController controller) {
+ assertTrue("Failed to flush handler!", controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ // Some AppStandbyController handler messages queue another handler message. Flush again
+ // to catch those as well.
+ assertTrue("Failed to flush handler (the second time)!",
+ controller.flushHandler(FLUSH_TIMEOUT_MILLISECONDS));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 2868b7e2bd4d..ae3683961d61 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -68,10 +68,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@ import java.util.concurrent.CountDownLatch;
public class NotificationListenersTest extends UiServiceTestCase {
@Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManager mPm;
@@ -696,8 +693,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withPermission() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
.thenReturn(PERMISSION_GRANTED);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withSystemSignature() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withCdmAssociation() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
AssociationInfo assocInfo = mock(AssociationInfo.class);
when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_ifFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
assertTrue(mListeners.isUidTrusted(mUid1));
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@ public class NotificationListenersTest extends UiServiceTestCase {
mListeners.notifyPostedLocked(r, old);
verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));
-
-
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted_oldRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
doReturn(mock(StatusBarNotification.class))
.when(mListeners).redactStatusBarNotification(any());
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_noneIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
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 9a1359591890..3ab7496eff84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,7 +77,9 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.Condition.SOURCE_CONTEXT;
@@ -216,9 +218,6 @@ import android.os.UserManager;
import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.DeniedDevices;
import android.platform.test.rule.DeviceProduct;
@@ -297,6 +296,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -360,9 +360,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
- @Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -9261,41 +9258,46 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_CONTEXT);
- mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
-
Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_USER_ACTION);
- mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser);
+
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
}
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_CONTEXT);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
- /* fromUser= */ true));
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_USER_ACTION);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
- /* fromUser= */ false));
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
}
private ZenModeHelper setUpMockZenTest() {
@@ -11777,8 +11779,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
mService.addNotification(r);
@@ -11807,12 +11809,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
- mService.addNotification(r);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
when(mListeners.hasSensitiveContent(any())).thenReturn(true);
StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11992,6 +11993,97 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgB);
+ mService.addNotification(pkgB);
+
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(0, ranking.getSmartActions().size());
+ assertEquals(0, ranking.getSmartReplies().size());
+ NotificationListenerService.Ranking ranking2 =
+ nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
+ assertEquals(0, ranking2.getSmartActions().size());
+ assertEquals(0, ranking2.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+
+ // No sensitive content, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(false);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+
+ // trusted listener, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ nru = mService.makeRankingUpdateLocked(info);
+ ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ private void addSmartActionsAndReplies(NotificationRecord record) {
+ Bundle b = new Bundle();
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ actions.add(new Notification.Action(0, "", null));
+ b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
+ ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
+ b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
+ Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
+ b, "", record.getUserId());
+ record.addAdjustment(a);
+ record.applyAdjustments();
+ }
+
+ @Test
public void testMaybeShowReviewPermissionsNotification_flagOff() {
mService.setShowReviewPermissionsNotification(false);
reset(mMockNm);
@@ -12302,7 +12394,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
/* isImageBitmap= */ true,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isTrue();
+ final Parcelable picture = record.getNotification().extras.getParcelable(EXTRA_PICTURE);
+ assertThat(picture).isNull();
}
@Test
@@ -12336,7 +12430,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
/* isImageBitmap= */ false,
/* isExpired= */ true);
addRecordAndRemoveBitmaps(record);
- assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isFalse();
+ assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isTrue();
+ final Parcelable pictureIcon =
+ record.getNotification().extras.getParcelable(EXTRA_PICTURE_ICON);
+ assertThat(pictureIcon).isNull();
}
@Test
@@ -13610,7 +13707,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
- public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_WATCH, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_autoCompanionApp_setsGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_otherCompanionApp_doesNotSetGlobalPolicy()
+ throws RemoteException {
+ setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+ }
+
+ private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+ @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+ throws RemoteException {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mService.setCallerIsNormalPackage();
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -13620,14 +13741,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mCompanionMgr.getAssociations(anyString(), anyInt()))
.thenReturn(ImmutableList.of(
new AssociationInfo.Builder(1, mUserId, "package")
- .setDisplayName("My watch")
- .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .setDisplayName("My connected device")
+ .setDeviceProfile(deviceProfile)
.build()));
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ if (canSetGlobalPolicy) {
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+ } else {
+ verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+ eq(policy), anyInt());
+ }
}
@Test
@@ -13697,7 +13823,29 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
- public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ public void setInterruptionFilter_watchCompanionApp_setsGlobalZen() throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_WATCH, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_autoCompanionApp_setsGlobalZen() throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_otherCompanionApp_doesNotSetGlobalZen()
+ throws RemoteException {
+ setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+ }
+
+ private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+ @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+ throws RemoteException {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
mService.mZenModeHelper = zenModeHelper;
@@ -13707,14 +13855,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mCompanionMgr.getAssociations(anyString(), anyInt()))
.thenReturn(ImmutableList.of(
new AssociationInfo.Builder(1, mUserId, "package")
- .setDisplayName("My watch")
- .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .setDisplayName("My connected device")
+ .setDeviceProfile(deviceProfile)
.build()));
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
- verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+ if (canSetGlobalPolicy) {
+ verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+ } else {
+ verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+ eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ }
}
@Test
@@ -13732,6 +13885,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne()
throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13761,6 +13915,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13788,6 +13943,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled()
throws RemoteException {
mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
@@ -13818,6 +13974,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll()
throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13846,6 +14003,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13872,6 +14030,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @Ignore("b/316989461")
public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled()
throws RemoteException {
mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
index d2ef1808652f..ca3787ec4546 100644
--- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -95,6 +95,26 @@ public final class DeferredKeyActionExecutorTests {
assertFalse(action.executed);
}
+ @Test
+ public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() {
+ TestAction action1 = new TestAction();
+ TestAction action2 = new TestAction();
+ TestAction action3 = new TestAction();
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1);
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2);
+ mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+ mKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3);
+
+ mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+ assertFalse(action1.executed);
+ assertFalse(action2.executed);
+ assertTrue(action3.executed);
+ }
+
static class TestAction implements Runnable {
public boolean executed;
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index f7ad2a8f5243..50d37ec7749b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -19,14 +19,18 @@ package com.android.server.policy;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS;
+import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS;
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY;
+import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.os.RemoteException;
import android.provider.Settings;
@@ -50,6 +54,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
public void stemSingleKey_duringSetup_doNothing() {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(false);
@@ -65,6 +70,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
public void stemSingleKey_AfterSetup_openAllApp() {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.overrideStartActivity();
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -83,6 +89,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
STEM_PRIMARY_BUTTON_SHORT_PRESS,
SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.overrideStartActivity();
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -104,6 +111,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
setDispatchedKeyHandler(keyEvent -> true);
sendKey(KEYCODE_STEM_PRIMARY);
@@ -131,6 +139,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
STEM_PRIMARY_BUTTON_LONG_PRESS,
LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -144,6 +153,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
STEM_PRIMARY_BUTTON_LONG_PRESS,
LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideSearchManager(null);
mPhoneWindowManager.overrideStatusBarManagerInternal();
@@ -156,7 +166,8 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
@Test
public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
throws RemoteException {
- overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
@@ -171,14 +182,47 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
@Test
- public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException {
+ public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
+ throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
+ mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ RootTaskInfo allAppsTask = new RootTaskInfo();
+ int referenceId = 777;
+ allAppsTask.taskId = referenceId;
+ doReturn(allAppsTask)
+ .when(mPhoneWindowManager.mActivityManagerService)
+ .getFocusedRootTaskInfo();
+
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertOpenAllAppView();
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+ }
+
+ @Test
+ public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
@@ -189,9 +233,16 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_STEM_PRIMARY);
sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertNotOpenAllAppView();
+ mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertNotOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
@Test
@@ -215,7 +266,7 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_STEM_PRIMARY);
mPhoneWindowManager.assertNotOpenAllAppView();
- mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ mPhoneWindowManager.assertSwitchToTask(referenceId);
}
private void overrideBehavior(String key, int expectedBehavior) {
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 0678210e1d2f..7c2f7eedff9d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -73,6 +73,7 @@ import android.media.AudioManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -176,8 +177,9 @@ class TestPhoneWindowManager {
private Handler mHandler;
private boolean mIsTalkBackEnabled;
+ private boolean mIsTalkBackShortcutGestureEnabled;
- class TestTalkbackShortcutController extends TalkbackShortcutController {
+ private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
}
@@ -190,13 +192,18 @@ class TestPhoneWindowManager {
@Override
boolean isTalkBackShortcutGestureEnabled() {
- return true;
+ return mIsTalkBackShortcutGestureEnabled;
}
}
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
- super(context, funcs, mTestLooper.getLooper());
+ super(context, funcs);
+ }
+
+ @Override
+ Looper getLooper() {
+ return mTestLooper.getLooper();
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -410,6 +417,10 @@ class TestPhoneWindowManager {
mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress;
}
+ void overrideTalkbackShortcutGestureEnabled(boolean enabled) {
+ mIsTalkBackShortcutGestureEnabled = enabled;
+ }
+
// Override assist perform function.
void overrideLongPressOnPower(int behavior) {
mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -714,7 +725,7 @@ class TestPhoneWindowManager {
}
void assertOpenAllAppView() {
- mTestLooper.dispatchAll();
+ moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -728,7 +739,7 @@ class TestPhoneWindowManager {
}
void assertActivityTargetLaunched(ComponentName targetActivity) {
- mTestLooper.dispatchAll();
+ moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -743,10 +754,15 @@ class TestPhoneWindowManager {
expectedModifierState, deviceBus), description(errorMsg));
}
- void assertSwitchToRecent(int persistentId) throws RemoteException {
+ void assertSwitchToTask(int persistentId) throws RemoteException {
mTestLooper.dispatchAll();
verify(mActivityManagerService,
timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId),
isNull());
}
+
+ void assertTalkBack(boolean expectEnabled) {
+ mTestLooper.dispatchAll();
+ Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 85c6f9e9b2fe..718c59875c1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -202,8 +202,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
- return new TestStartingWindowOrganizer(mAtm,
- mSystemServicesTestRule.getPowerManagerWrapper());
+ return new TestStartingWindowOrganizer(mAtm);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index ef427bb15039..a8b217860946 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.server.wm.CtsWindowInfoUtils.dumpWindowsOnScreen;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -129,37 +129,22 @@ public class SurfaceControlViewHostTests {
mScvh2.setView(mView2, lp2);
});
- boolean wasVisible = waitForWindowVisible(mView1);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows");
- }
- assertTrue("Failed to wait for view1", wasVisible);
-
- wasVisible = waitForWindowVisible(mView2);
- if (!wasVisible) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-not visible");
- }
- assertTrue("Failed to wait for view2", wasVisible);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1", waitForWindowVisible(mView1));
+ assertAndDumpWindowState(TAG, "Failed to wait for view2", waitForWindowVisible(mView2));
IWindow window = IWindow.Stub.asInterface(mSurfaceView.getWindowToken());
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh1.getInputTransferToken(), true);
- boolean gainedFocus = waitForWindowFocus(mView1, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view1 not focus");
- }
- assertTrue("Failed to gain focus for view1", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view1 focus",
+ waitForWindowFocus(mView1, true));
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(window,
mScvh2.getInputTransferToken(), true);
- gainedFocus = waitForWindowFocus(mView2, true);
- if (!gainedFocus) {
- dumpWindowsOnScreen(TAG, "requestFocusWithMultipleWindows-view2 not focus");
- }
- assertTrue("Failed to gain focus for view2", gainedFocus);
+ assertAndDumpWindowState(TAG, "Failed to wait for view2 focus",
+ waitForWindowFocus(mView2, true));
}
private static class TestWindowlessWindowManager extends WindowlessWindowManager {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 51f0404e2396..8cd9ff359111 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,7 +134,6 @@ public class SystemServicesTestRule implements TestRule {
private StaticMockitoSession mMockitoSession;
private ActivityTaskManagerService mAtmService;
private WindowManagerService mWmService;
- private WindowState.PowerManagerWrapper mPowerManagerWrapper;
private InputManagerService mImService;
private InputChannel mInputChannel;
private Runnable mOnBeforeServicesCreated;
@@ -360,7 +359,6 @@ public class SystemServicesTestRule implements TestRule {
}
private void setUpWindowManagerService() {
- mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy();
TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider =
new TestDisplayWindowSettingsProvider();
@@ -485,10 +483,6 @@ public class SystemServicesTestRule implements TestRule {
return mAtmService;
}
- WindowState.PowerManagerWrapper getPowerManagerWrapper() {
- return mPowerManagerWrapper;
- }
-
/** Creates a no-op wakelock object. */
PowerManager.WakeLock createStubbedWakeLock(boolean needVerification) {
if (needVerification) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index ac498397eb39..6a15b0594428 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -138,11 +139,8 @@ public class TrustedOverlayTests {
return false;
}, TIMEOUT_S, TimeUnit.SECONDS);
- if (!foundTrusted[0]) {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
- }
-
- assertTrue("Failed to find window or was not marked trusted", foundTrusted[0]);
+ assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
+ foundTrusted[0]);
}
private void testTrustedOverlayChildHelper(boolean expectedTrustedChild)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 2007f680f1ae..75e252f9a415 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -52,7 +52,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -368,28 +367,26 @@ public class WindowStateTests extends WindowTestsBase {
firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
- final WindowState.PowerManagerWrapper powerManagerWrapper =
- mSystemServicesTestRule.getPowerManagerWrapper();
- reset(powerManagerWrapper);
+ final var powerManager = mWm.mPowerManager;
+ clearInvocations(powerManager);
firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
- reset(powerManagerWrapper);
+ clearInvocations(powerManager);
secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
}
private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) {
- final WindowState.PowerManagerWrapper powerManagerWrapper =
- mSystemServicesTestRule.getPowerManagerWrapper();
- reset(powerManagerWrapper);
+ final var powerManager = mWm.mPowerManager;
+ clearInvocations(powerManager);
appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
if (expectedWakeupCalled) {
- verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
} else {
- verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
+ verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
// If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
// because the state will be consumed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 616a23e7ab5b..a5f6190f2d51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -637,14 +637,12 @@ class WindowTestsBase extends SystemServiceTestsBase {
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
- ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow,
- mSystemServicesTestRule.getPowerManagerWrapper());
+ ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
}
static WindowState createWindow(WindowState parent, int type, WindowToken token,
String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
- WindowManagerService service, Session session, IWindow iWindow,
- WindowState.PowerManagerWrapper powerManagerWrapper) {
+ WindowManagerService service, Session session, IWindow iWindow) {
SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
@@ -652,9 +650,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
attrs.packageName = "test";
final WindowState w = new WindowState(service, session, iWindow, token, parent,
- OP_NONE, attrs, VISIBLE, ownerId, userId,
- ownerCanAddInternalSystemWindow,
- powerManagerWrapper);
+ OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);
@@ -1738,17 +1734,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer {
private final ActivityTaskManagerService mAtm;
private final WindowManagerService mWMService;
- private final WindowState.PowerManagerWrapper mPowerManagerWrapper;
private Runnable mRunnableWhenAddingSplashScreen;
private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
- TestStartingWindowOrganizer(ActivityTaskManagerService service,
- WindowState.PowerManagerWrapper powerManagerWrapper) {
+ TestStartingWindowOrganizer(ActivityTaskManagerService service) {
mAtm = service;
mWMService = mAtm.mWindowManager;
- mPowerManagerWrapper = powerManagerWrapper;
mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
}
@@ -1767,8 +1760,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
final WindowState window = WindowTestsBase.createWindow(null,
TYPE_APPLICATION_STARTING, activity,
"Starting window", 0 /* ownerId */, 0 /* userId*/,
- false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow,
- mPowerManagerWrapper);
+ false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
activity.mStartingWindow = window;
mAppWindowMap.put(info.appToken, window);
mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 4c978ad01a81..2445f51a4241 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.usage.ExternalStorageStats;
+import android.app.usage.Flags;
import android.app.usage.IStorageStatsManager;
import android.app.usage.StorageStats;
import android.app.usage.UsageStatsManagerInternal;
@@ -434,6 +435,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
final long[] ceDataInodes = new long[packageNames.length];
String[] codePaths = new String[0];
+ final PackageStats stats = new PackageStats(TAG);
for (int i = 0; i < packageNames.length; i++) {
try {
final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
@@ -443,7 +445,11 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
} else {
if (appInfo.getCodePath() != null) {
codePaths = ArrayUtils.appendElement(String.class, codePaths,
- appInfo.getCodePath());
+ appInfo.getCodePath());
+ }
+ if (Flags.getAppBytesByDataTypeApi()) {
+ computeAppStatsByDataTypes(
+ stats, appInfo.sourceDir);
}
}
} catch (NameNotFoundException e) {
@@ -451,7 +457,6 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
}
}
- final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
appId, ceDataInodes, codePaths, stats);
@@ -587,6 +592,9 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+ res.apkBytes = stats.apkSize;
+ res.libBytes = stats.libSize;
+ res.dmBytes = stats.dmSize;
res.externalCacheBytes = stats.externalCacheSize;
return res;
}
@@ -894,4 +902,61 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
}
}
+
+ private long getDirBytes(File dir) {
+ if (!dir.isDirectory()) {
+ return 0;
+ }
+
+ long size = 0;
+ try {
+ for (File file : dir.listFiles()) {
+ if (file.isFile()) {
+ size += file.length();
+ continue;
+ }
+ if (file.isDirectory()) {
+ size += getDirBytes(file);
+ }
+ }
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed to list directory " + dir.getName());
+ }
+
+ return size;
+ }
+
+ private long getFileBytesInDir(File dir, String suffix) {
+ if (!dir.isDirectory()) {
+ return 0;
+ }
+
+ long size = 0;
+ try {
+ for (File file : dir.listFiles()) {
+ if (file.isFile() && file.getName().endsWith(suffix)) {
+ size += file.length();
+ }
+ }
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed to list directory " + dir.getName());
+ }
+
+ return size;
+ }
+
+ private void computeAppStatsByDataTypes(
+ PackageStats stats, String sourceDirName) {
+
+ // Get apk, lib, dm file sizes.
+ File srcDir = new File(sourceDirName);
+ if (srcDir.isFile()) {
+ sourceDirName = srcDir.getParent();
+ srcDir = new File(sourceDirName);
+ }
+
+ stats.apkSize += getFileBytesInDir(srcDir, ".apk");
+ stats.dmSize += getFileBytesInDir(srcDir, ".dm");
+ stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ccd4ce038367..08f719e91da9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1121,13 +1121,8 @@ public class UsageStatsService extends SystemService implements
switch (event.mEventType) {
case Event.ACTIVITY_RESUMED:
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_RESUMED, uid, event.mPackage);
+
// check if this activity has already been resumed
if (mVisibleActivities.get(event.mInstanceId) != null) break;
final String usageSourcePackage = getUsageSourcePackage(event);
@@ -1172,13 +1167,8 @@ public class UsageStatsService extends SystemService implements
usageSourcePackage2);
mVisibleActivities.put(event.mInstanceId, pausedData);
} else {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+ event.mPackage);
}
pausedData.lastEvent = Event.ACTIVITY_PAUSED;
@@ -1203,13 +1193,8 @@ public class UsageStatsService extends SystemService implements
}
if (prevData.lastEvent != Event.ACTIVITY_PAUSED) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED,
- uid,
- event.mPackage,
- "",
- FrameworkStatsLog
- .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND);
+ logAppUsageEventReportedAtomLocked(Event.ACTIVITY_PAUSED, uid,
+ event.mPackage);
}
ArraySet<String> tokens;
@@ -1244,11 +1229,19 @@ public class UsageStatsService extends SystemService implements
}
break;
case Event.USER_INTERACTION:
- // Fall through
+ logAppUsageEventReportedAtomLocked(Event.USER_INTERACTION, uid, event.mPackage);
+ // Fall through.
case Event.APP_COMPONENT_USED:
convertToSystemTimeLocked(event);
mLastTimeComponentUsedGlobal.put(event.mPackage, event.mTimeStamp);
break;
+ case Event.SHORTCUT_INVOCATION:
+ case Event.CHOOSER_ACTION:
+ case Event.STANDBY_BUCKET_CHANGED:
+ case Event.FOREGROUND_SERVICE_START:
+ case Event.FOREGROUND_SERVICE_STOP:
+ logAppUsageEventReportedAtomLocked(event.mEventType, uid, event.mPackage);
+ break;
}
final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
@@ -1261,6 +1254,45 @@ public class UsageStatsService extends SystemService implements
mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget();
}
+ @GuardedBy("mLock")
+ private void logAppUsageEventReportedAtomLocked(int eventType, int uid, String packageName) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, uid, packageName,
+ "", getAppUsageEventOccurredAtomEventType(eventType));
+ }
+
+ /** Make sure align with the EventType defined in the AppUsageEventOccurred atom. */
+ private int getAppUsageEventOccurredAtomEventType(int eventType) {
+ switch (eventType) {
+ case Event.ACTIVITY_RESUMED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND;
+ case Event.ACTIVITY_PAUSED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_BACKGROUND;
+ case Event.USER_INTERACTION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__USER_INTERACTION;
+ case Event.SHORTCUT_INVOCATION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__SHORTCUT_INVOCATION;
+ case Event.CHOOSER_ACTION:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__CHOOSER_ACTION;
+ case Event.STANDBY_BUCKET_CHANGED:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__STANDBY_BUCKET_CHANGED;
+ case Event.FOREGROUND_SERVICE_START:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_START;
+ case Event.FOREGROUND_SERVICE_STOP:
+ return FrameworkStatsLog
+ .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__FOREGROUND_SERVICE_STOP;
+ default:
+ Slog.w(TAG, "Unsupported usage event logging: " + eventType);
+ return -1;
+ }
+ }
+
private String getUsageSourcePackage(Event event) {
switch(mUsageSource) {
case USAGE_SOURCE_CURRENT_ACTIVITY:
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 1dc5dcf320e0..3a0a6abb2307 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -35,4 +35,7 @@ java_library_static {
"android.hardware.usb-V1.3-java",
"android.hardware.usb-V3-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/telecomm/java/android/telecom/AuthenticatorService.java b/telecomm/java/android/telecom/AuthenticatorService.java
deleted file mode 100644
index 1e43c7157580..000000000000
--- a/telecomm/java/android/telecom/AuthenticatorService.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.telecom;
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.NetworkErrorException;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-
-/**
- * A generic stub account authenticator service often used for sync adapters that do not directly
- * involve accounts.
- *
- * @hide
- */
-public class AuthenticatorService extends Service {
- private static Authenticator mAuthenticator;
-
- @Override
- public void onCreate() {
- mAuthenticator = new Authenticator(this);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mAuthenticator.getIBinder();
- }
-
- /**
- * Stub account authenticator. All methods either return null or throw an exception.
- */
- public class Authenticator extends AbstractAccountAuthenticator {
- public Authenticator(Context context) {
- super(context);
- }
-
- @Override
- public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
- String s) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse,
- String s, String s2, String[] strings, Bundle bundle)
- throws NetworkErrorException {
- return null;
- }
-
- @Override
- public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, Bundle bundle)
- throws NetworkErrorException {
- return null;
- }
-
- @Override
- public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String s, Bundle bundle)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getAuthTokenLabel(String s) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String s, Bundle bundle)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
- Account account, String[] strings)
- throws NetworkErrorException {
- throw new UnsupportedOperationException();
- }
- }
-}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index def52a517913..874c10c8ea83 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -210,100 +210,6 @@ public final class Call {
"android.telecom.extra.SILENT_RINGING_REQUESTED";
/**
- * Call event sent from a {@link Call} via {@link #sendCallEvent(String, Bundle)} to inform
- * Telecom that the user has requested that the current {@link Call} should be handed over
- * to another {@link ConnectionService}.
- * <p>
- * The caller must specify the {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE} to indicate to
- * Telecom which {@link PhoneAccountHandle} the {@link Call} should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_REQUEST_HANDOVER =
- "android.telecom.event.REQUEST_HANDOVER";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * {@link PhoneAccountHandle} to which a call should be handed over to.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE =
- "android.telecom.extra.HANDOVER_PHONE_ACCOUNT_HANDLE";
-
- /**
- * Integer extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Specifies the
- * video state of the call when it is handed over to the new {@link PhoneAccount}.
- * <p>
- * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY},
- * {@link VideoProfile#STATE_BIDIRECTIONAL}, {@link VideoProfile#STATE_RX_ENABLED}, and
- * {@link VideoProfile#STATE_TX_ENABLED}.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_VIDEO_STATE =
- "android.telecom.extra.HANDOVER_VIDEO_STATE";
-
- /**
- * Extra key used with the {@link #EVENT_REQUEST_HANDOVER} call event. Used by the
- * {@link InCallService} initiating a handover to provide a {@link Bundle} with extra
- * information to the handover {@link ConnectionService} specified by
- * {@link #EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE}.
- * <p>
- * This {@link Bundle} is not interpreted by Telecom, but passed as-is to the
- * {@link ConnectionService} via the request extras when
- * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
- * is called to initate the handover.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EXTRA_HANDOVER_EXTRAS = "android.telecom.extra.HANDOVER_EXTRAS";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has completed successfully.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Call event sent from Telecom to the handover destination {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform the handover destination that the
- * source connection has disconnected. The {@link Bundle} parameter for the call event will be
- * {@code null}.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_SOURCE_DISCONNECTED =
- "android.telecom.event.HANDOVER_SOURCE_DISCONNECTED";
-
- /**
- * Call event sent from Telecom to the handover {@link ConnectionService} via
- * {@link Connection#onCallEvent(String, Bundle)} to inform a {@link Connection} that a handover
- * to the {@link ConnectionService} has failed.
- * <p>
- * A handover is initiated with the {@link #EVENT_REQUEST_HANDOVER} call event.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* Event reported from the Telecom stack to report an in-call diagnostic message which the
* dialer app may opt to display to the user. A diagnostic message is used to communicate
* scenarios the device has detected which may impact the quality of the ongoing call.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4a541da9e5aa..a2105b02b97a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -961,28 +961,6 @@ public abstract class Connection extends Conferenceable {
"android.telecom.event.CALL_REMOTELY_UNHELD";
/**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has
- * successfully completed.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_COMPLETE =
- "android.telecom.event.HANDOVER_COMPLETE";
-
- /**
- * Connection event used to inform an {@link InCallService} which initiated a call handover via
- * {@link Call#EVENT_REQUEST_HANDOVER} that the handover from this {@link Connection} has failed
- * to complete.
- * @hide
- * @deprecated Use {@link Call#handoverTo(PhoneAccountHandle, int, Bundle)} and its associated
- * APIs instead.
- */
- public static final String EVENT_HANDOVER_FAILED =
- "android.telecom.event.HANDOVER_FAILED";
-
- /**
* String Connection extra key used to store SIP invite fields for an incoming call for IMS call
*/
public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
@@ -3362,15 +3340,6 @@ public abstract class Connection extends Conferenceable {
public void onDisconnect() {}
/**
- * Notifies this Connection of a request to disconnect a participant of the conference managed
- * by the connection.
- *
- * @param endpoint the {@link Uri} of the participant to disconnect.
- * @hide
- */
- public void onDisconnectConferenceParticipant(Uri endpoint) {}
-
- /**
* Notifies this Connection of a request to separate from its parent conference.
*/
public void onSeparate() {}
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index db38f8873a02..575ec27622a5 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -187,14 +187,15 @@ public final class AnomalyReporter {
}
for (ResolveInfo r : packages) {
- if (r.activityInfo == null
- || pm.checkPermission(
+ if (r.activityInfo == null) {
+ Rlog.w(TAG, "Found package without activity");
+ continue;
+ } else if (pm.checkPermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
r.activityInfo.packageName)
- != PackageManager.PERMISSION_GRANTED) {
- Rlog.w(TAG,
- "Found package without proper permissions or no activity"
- + r.activityInfo.packageName);
+ != PackageManager.PERMISSION_GRANTED) {
+ Rlog.w(TAG, "Found package without proper permissions"
+ + r.activityInfo.packageName);
continue;
}
Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bcd99295b605..c7b84a3b9530 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -525,6 +525,12 @@ public class CarrierConfigManager {
public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
/**
+ * Used in the Preferred Network Types menu to determine if the 3G option is displayed.
+ */
+ @FlaggedApi(Flags.FLAG_HIDE_PREFER_3G_ITEM)
+ public static final String KEY_PREFER_3G_VISIBILITY_BOOL = "prefer_3g_visibility_bool";
+
+ /**
* Used in Cellular Network Settings for preferred network type to show 4G only mode.
* @hide
*/
@@ -3715,19 +3721,19 @@ public class CarrierConfigManager {
* This configuration allows the system UI to display different 5G icons for different 5G
* scenarios.
*
- * There are five 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
+ * There are six 5G scenarios for icon configuration:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
*
* The configured string contains multiple key-value pairs separated by comma. For each pair,
* the key and value are separated by a colon. The key corresponds to a 5G status above and
@@ -3748,21 +3754,21 @@ public class CarrierConfigManager {
* This configuration allows the system UI to determine how long to continue to display 5G icons
* when the device switches between different 5G scenarios.
*
- * There are seven 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
- * 6. legacy: device is not camped on a network that has 5G capability
- * 7. any: any of the above scenarios
+ * There are eight 5G scenarios:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
+ * 7. legacy: device is not camped on a network that has 5G capability
+ * 8. any: any of the above scenarios
*
* The configured string contains various timer rules separated by a semicolon.
* Each rule will have three items: prior 5G scenario, current 5G scenario, and grace period
@@ -3770,8 +3776,8 @@ public class CarrierConfigManager {
* 5G scenario, the system UI will continue to show the icon for the prior 5G scenario (defined
* in {@link #KEY_5G_ICON_CONFIGURATION_STRING}) for the amount of time specified by the grace
* period. If the prior 5G scenario is reestablished, the timer will reset and start again if
- * the UE changes 5G scenarios again. Defined states (5G scenarios #1-5) take precedence over
- * 'any' (5G scenario #6), and unspecified transitions have a default grace period of 0.
+ * the UE changes 5G scenarios again. Defined states (5G scenarios #1-7) take precedence over
+ * 'any' (5G scenario #8), and unspecified transitions have a default grace period of 0.
* The order of rules in the configuration determines the priority (the first applicable timer
* rule will be used).
*
@@ -3794,21 +3800,21 @@ public class CarrierConfigManager {
* This configuration extends {@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING} to allow the
* system UI to continue displaying 5G icons after the initial timer expires.
*
- * There are seven 5G scenarios:
- * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
- * millimeter wave.
- * 2. connected: device currently connected to 5G cell as the secondary cell but not using
- * millimeter wave.
- * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in IDLE state.
- * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
- * to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
- * currently in CONNECTED state.
- * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
- * 5G cell as a secondary cell) but the use of 5G is restricted.
- * 6. legacy: device is not camped on a network that has 5G capability
- * 7. any: any of the above scenarios
+ * There are eight 5G scenarios:
+ * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+ * and considered NR advanced.
+ * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+ * considered NR advanced.
+ * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+ * and RRC currently in IDLE state.
+ * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in IDLE state.
+ * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+ * 5G is not restricted and RRC currently in CONNECTED state.
+ * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+ * restricted.
+ * 7. legacy: device is not camped on a network that has 5G capability
+ * 8. any: any of the above scenarios
*
* The configured string contains various timer rules separated by a semicolon.
* Each rule will have three items: primary 5G scenario, secondary 5G scenario, and
@@ -3818,7 +3824,7 @@ public class CarrierConfigManager {
* period. If the primary 5G scenario is reestablished, the timers will reset and the system UI
* will continue to display the icon for the primary 5G scenario without interruption. If the
* secondary 5G scenario is lost, the timer will reset and the icon will reflect the true state.
- * Defined states (5G scenarios #1-5) take precedence over 'any' (5G scenario #6), and
+ * Defined states (5G scenarios #1-7) take precedence over 'any' (5G scenario #8), and
* unspecified transitions have a default grace period of 0. The order of rules in the
* configuration determines the priority (the first applicable timer rule will be used).
*
@@ -8885,18 +8891,18 @@ public class CarrierConfigManager {
KEY_PREFIX + "epdg_static_address_roaming_string";
/**
- * Controls if the multiple SA proposals allowed for IKE session to include
- * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
- * IKE SA proposals as per RFC 7296.
+ * Enables the use of multiple IKE SA proposals, encompassing both carrier-preferred
+ * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+ * as defined in RFC 7296.
*/
@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool";
/**
- * Controls if the multiple SA proposals allowed for Child session to include
- * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
- * Child SA proposals as per RFC 7296.
+ * Enables the use of multiple Child SA proposals, encompassing both carrier-preferred
+ * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+ * as defined in RFC 7296.
*/
@FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
@@ -10044,6 +10050,49 @@ public class CarrierConfigManager {
public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE =
"auto_data_switch_rat_signal_score_string_bundle";
+ // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+ /**
+ * An array of cellular services supported by a subscription.
+ *
+ * <p>Permissible values include:
+ * <ul>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} for voice services</li>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_SMS} for SMS services</li>
+ * <li>{@code SubscriptionManager#SERVICE_CAPABILITY_DATA} for data services</li>
+ * </ul>
+ *
+ * <p>Carrier-specific factors may influence how these services are supported. Therefore,
+ * modifying this carrier configuration might not always enable the specified services. These
+ * capability bitmasks should be considered as indicators of a carrier's preferred services
+ * to enhance user experience, rather than as absolute platform guarantees.
+ *
+ * <p>Device-level service capabilities, defined by
+ * {@code TelephonyManager#isDeviceVoiceCapable} and
+ * {@code TelephonyManager#isDeviceSmsCapable}, take precedence over these subscription-level
+ * settings. For instance, a device where {@code TelephonyManager#isDeviceVoiceCapable} returns
+ * false may not be able to make voice calls, even if subscribed to a service marked as
+ * voice-capable.
+ *
+ * <p>To determine a subscription's cellular service capabilities, use
+ * {@code SubscriptionInfo#getServiceCapabilities()}. To track changes in services, register
+ * a {@link SubscriptionManager.OnSubscriptionsChangedListener} and invoke the
+ * same method in its callback.
+ *
+ * <p>Emergency service availability may not depend on the cellular service capabilities.
+ * For example, emergency calls might be possible on a subscription even if it lacks
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ *
+ * <p>If unset, the default value is “[1, 2, 3]” (supports all cellular services).
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable
+ * @see TelephonyManager#isDeviceSmsCapable
+ * @see SubscriptionInfo#getServiceCapabilities()
+ * @see SubscriptionManager.OnSubscriptionsChangedListener
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
+ "cellular_service_capabilities_int_array";
+
/** The default value for every variable. */
private static final PersistableBundle sDefaults;
@@ -10144,6 +10193,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
sDefaults.putBoolean(KEY_PREFER_2G_BOOL, false);
+ sDefaults.putBoolean(KEY_PREFER_3G_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_4G_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false);
@@ -10558,7 +10608,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_USE_CALL_WAITING_USSD_BOOL, false);
sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING,
- "connected_mmwave:5G,connected:5G,not_restricted_rrc_idle:5G,"
+ "connected_mmwave:5G,connected:5G,connected_rrc_idle:5G,not_restricted_rrc_idle:5G,"
+ "not_restricted_rrc_con:5G");
sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "");
sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
@@ -10830,6 +10880,7 @@ public class CarrierConfigManager {
new boolean[] {false, false, true, false, false});
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
+ sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
}
/**
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
new file mode 100644
index 000000000000..61d7ead67a21
--- /dev/null
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A single occurrence capturing a notable change to previously reported
+ * cryptography algorithms for a given network and network event.
+ *
+ * @hide
+ */
+public final class SecurityAlgorithmUpdate implements Parcelable {
+ private static final String TAG = "SecurityAlgorithmUpdate";
+
+ private @ConnectionEvent int mConnectionEvent;
+ private @SecurityAlgorithm int mEncryption;
+ private @SecurityAlgorithm int mIntegrity;
+ private boolean mIsUnprotectedEmergency;
+
+ public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent,
+ @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity,
+ boolean isUnprotectedEmergency) {
+ mConnectionEvent = connectionEvent;
+ mEncryption = encryption;
+ mIntegrity = integrity;
+ mIsUnprotectedEmergency = isUnprotectedEmergency;
+ }
+
+ private SecurityAlgorithmUpdate(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public @ConnectionEvent int getConnectionEvent() {
+ return mConnectionEvent;
+ }
+
+ public @SecurityAlgorithm int getEncryption() {
+ return mEncryption;
+ }
+
+ public @SecurityAlgorithm int getIntegrity() {
+ return mIntegrity;
+ }
+
+ public boolean isUnprotectedEmergency() {
+ return mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mConnectionEvent);
+ out.writeInt(mEncryption);
+ out.writeInt(mIntegrity);
+ out.writeBoolean(mIsUnprotectedEmergency);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mConnectionEvent = in.readInt();
+ mEncryption = in.readInt();
+ mIntegrity = in.readInt();
+ mIsUnprotectedEmergency = in.readBoolean();
+ }
+
+ public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR =
+ new Parcelable.Creator<SecurityAlgorithmUpdate>() {
+ public SecurityAlgorithmUpdate createFromParcel(Parcel in) {
+ return new SecurityAlgorithmUpdate(in);
+ }
+
+ public SecurityAlgorithmUpdate[] newArray(int size) {
+ return new SecurityAlgorithmUpdate[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption
+ + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = "
+ + mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SecurityAlgorithmUpdate)) return false;
+ SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o;
+ return mConnectionEvent == that.mConnectionEvent
+ && mEncryption == that.mEncryption
+ && mIntegrity == that.mIntegrity
+ && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency);
+ }
+
+ public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0;
+ public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1;
+ public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2;
+ public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3;
+ public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
+ public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
+ public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
+ public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
+ public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
+ public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
+ public static final int CONNECTION_EVENT_VONR_SIP = 10;
+ public static final int CONNECTION_EVENT_VONR_RTP = 11;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {CONNECTION_EVENT_CS_SIGNALLING_GSM,
+ CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
+ CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
+ CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
+ CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+ CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
+ CONNECTION_EVENT_VONR_RTP})
+ public @interface ConnectionEvent {
+ }
+
+ public static final int SECURITY_ALGORITHM_A50 = 0;
+ public static final int SECURITY_ALGORITHM_A51 = 1;
+ public static final int SECURITY_ALGORITHM_A52 = 2;
+ public static final int SECURITY_ALGORITHM_A53 = 3;
+ public static final int SECURITY_ALGORITHM_A54 = 4;
+ public static final int SECURITY_ALGORITHM_GEA0 = 14;
+ public static final int SECURITY_ALGORITHM_GEA1 = 15;
+ public static final int SECURITY_ALGORITHM_GEA2 = 16;
+ public static final int SECURITY_ALGORITHM_GEA3 = 17;
+ public static final int SECURITY_ALGORITHM_GEA4 = 18;
+ public static final int SECURITY_ALGORITHM_GEA5 = 19;
+ public static final int SECURITY_ALGORITHM_UEA0 = 29;
+ public static final int SECURITY_ALGORITHM_UEA1 = 30;
+ public static final int SECURITY_ALGORITHM_UEA2 = 31;
+ public static final int SECURITY_ALGORITHM_EEA0 = 41;
+ public static final int SECURITY_ALGORITHM_EEA1 = 42;
+ public static final int SECURITY_ALGORITHM_EEA2 = 43;
+ public static final int SECURITY_ALGORITHM_EEA3 = 44;
+ public static final int SECURITY_ALGORITHM_NEA0 = 55;
+ public static final int SECURITY_ALGORITHM_NEA1 = 56;
+ public static final int SECURITY_ALGORITHM_NEA2 = 57;
+ public static final int SECURITY_ALGORITHM_NEA3 = 58;
+ public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
+ public static final int SECURITY_ALGORITHM_AES_GCM = 69;
+ public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
+ public static final int SECURITY_ALGORITHM_AES_CBC = 71;
+ public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
+ public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
+ public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
+ public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+ public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
+ public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
+ public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
+ public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99;
+ public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100;
+ public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101;
+ public static final int SECURITY_ALGORITHM_UNKNOWN = 113;
+ public static final int SECURITY_ALGORITHM_OTHER = 114;
+ public static final int SECURITY_ALGORITHM_ORYX = 124;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {SECURITY_ALGORITHM_A50, SECURITY_ALGORITHM_A51,
+ SECURITY_ALGORITHM_A52, SECURITY_ALGORITHM_A53,
+ SECURITY_ALGORITHM_A54, SECURITY_ALGORITHM_GEA0, SECURITY_ALGORITHM_GEA1,
+ SECURITY_ALGORITHM_GEA2, SECURITY_ALGORITHM_GEA3, SECURITY_ALGORITHM_GEA4,
+ SECURITY_ALGORITHM_GEA5, SECURITY_ALGORITHM_UEA0, SECURITY_ALGORITHM_UEA1,
+ SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
+ SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
+ SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
+ SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+ SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
+ SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
+ SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
+ SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
+ SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+ SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+ SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+ SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+ public @interface SecurityAlgorithm {
+ }
+
+}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 6ebf3bea7b6d..a188581ef695 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -54,6 +54,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* A Parcelable class for Subscription Information.
@@ -262,6 +263,11 @@ public class SubscriptionInfo implements Parcelable {
private final boolean mIsOnlyNonTerrestrialNetwork;
/**
+ * The service capabilities (in the form of bitmask combination) the subscription supports.
+ */
+ private final int mServiceCapabilities;
+
+ /**
* @hide
*
* @deprecated Use {@link SubscriptionInfo.Builder}.
@@ -386,6 +392,7 @@ public class SubscriptionInfo implements Parcelable {
this.mPortIndex = portIndex;
this.mUsageSetting = usageSetting;
this.mIsOnlyNonTerrestrialNetwork = false;
+ this.mServiceCapabilities = 0;
}
/**
@@ -425,6 +432,7 @@ public class SubscriptionInfo implements Parcelable {
this.mPortIndex = builder.mPortIndex;
this.mUsageSetting = builder.mUsageSetting;
this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
+ this.mServiceCapabilities = builder.mServiceCapabilities;
}
/**
@@ -882,6 +890,44 @@ public class SubscriptionInfo implements Parcelable {
return mIsOnlyNonTerrestrialNetwork;
}
+ // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+ /**
+ * Retrieves the service capabilities for the current subscription.
+ *
+ * <p>These capabilities are hint to system components and applications, allowing them to
+ * enhance user experience. For instance, a Dialer application can inform the user that the
+ * current subscription is incapable of making voice calls if the voice service is not
+ * available.
+ *
+ * <p>Correct usage of these service capabilities must also consider the device's overall
+ * service capabilities. For example, even if the subscription supports voice calls, a voice
+ * call might not be feasible on a device that only supports data services. To determine the
+ * device's capabilities for voice and SMS services, refer to
+ * {@code TelephonyManager#isDeviceVoiceCapable()} and
+ * {@code TelephonyManager#isDeviceSmsCapable()}.
+ *
+ * <p>Emergency service availability may not directly correlate with the subscription or
+ * device's general service capabilities. In some cases, emergency calls might be possible
+ * even if the subscription or device does not typically support voice services.
+ *
+ * @return A set of integer representing the subscription's service capabilities,
+ * defined by {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE},
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS}
+ * and {@code SubscriptionManager#SERVICE_CAPABILITY_DATA}.
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable()
+ * @see TelephonyManager#isDeviceSmsCapable()
+ * @see CarrierConfigManager#KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY
+ * @see SubscriptionManager#SERVICE_CAPABILITY_VOICE
+ * @see SubscriptionManager#SERVICE_CAPABILITY_SMS
+ * @see SubscriptionManager#SERVICE_CAPABILITY_DATA
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() {
+ return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
+ }
+
@NonNull
public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
new Parcelable.Creator<SubscriptionInfo>() {
@@ -919,6 +965,8 @@ public class SubscriptionInfo implements Parcelable {
.setUiccApplicationsEnabled(source.readBoolean())
.setUsageSetting(source.readInt())
.setOnlyNonTerrestrialNetwork(source.readBoolean())
+ .setServiceCapabilities(
+ SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
.build();
}
@@ -961,6 +1009,7 @@ public class SubscriptionInfo implements Parcelable {
dest.writeBoolean(mAreUiccApplicationsEnabled);
dest.writeInt(mUsageSetting);
dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
+ dest.writeInt(mServiceCapabilities);
}
@Override
@@ -1024,6 +1073,8 @@ public class SubscriptionInfo implements Parcelable {
+ " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
+ " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting)
+ " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+ + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
+ mServiceCapabilities).toString()
+ "]";
}
@@ -1049,7 +1100,8 @@ public class SubscriptionInfo implements Parcelable {
that.mNativeAccessRules) && Arrays.equals(mCarrierConfigAccessRules,
that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
&& mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
- && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork;
+ && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
+ && mServiceCapabilities == that.mServiceCapabilities;
}
@Override
@@ -1058,7 +1110,7 @@ public class SubscriptionInfo implements Parcelable {
mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
- mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork);
+ mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
result = 31 * result + Arrays.hashCode(mEhplmns);
result = 31 * result + Arrays.hashCode(mHplmns);
result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1263,6 +1315,11 @@ public class SubscriptionInfo implements Parcelable {
private boolean mIsOnlyNonTerrestrialNetwork = false;
/**
+ * Service capabilities bitmasks the subscription supports.
+ */
+ private int mServiceCapabilities = 0;
+
+ /**
* Default constructor.
*/
public Builder() {
@@ -1305,6 +1362,7 @@ public class SubscriptionInfo implements Parcelable {
mPortIndex = info.mPortIndex;
mUsageSetting = info.mUsageSetting;
mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
+ mServiceCapabilities = info.mServiceCapabilities;
}
/**
@@ -1703,6 +1761,32 @@ public class SubscriptionInfo implements Parcelable {
}
/**
+ * Set the service capabilities that the subscription supports.
+ *
+ * @param capabilities Bitmask combination of SubscriptionManager
+ * .SERVICE_CAPABILITY_XXX.
+ * @return The builder.
+ *
+ * @throws IllegalArgumentException when any capability is not supported.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public Builder setServiceCapabilities(
+ @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) {
+ int combinedCapabilities = 0;
+ for (int capability : capabilities) {
+ if (capability < SubscriptionManager.SERVICE_CAPABILITY_VOICE
+ || capability > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
+ throw new IllegalArgumentException(
+ "Invalid service capability value: " + capability);
+ }
+ combinedCapabilities |= SubscriptionManager.serviceCapabilityToBitmask(capability);
+ }
+ mServiceCapabilities = combinedCapabilities;
+ return this;
+ }
+
+ /**
* Build the {@link SubscriptionInfo}.
*
* @return The {@link SubscriptionInfo} instance.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 326b6f5af613..6c8663a8eb14 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -88,10 +88,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -1138,6 +1140,14 @@ public class SubscriptionManager {
*/
public static final String IS_NTN = SimInfo.COLUMN_IS_NTN;
+ /**
+ * TelephonyProvider column name to identify service capabilities.
+ * Disabled by default.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
@@ -1347,6 +1357,86 @@ public class SubscriptionManager {
})
public @interface PhoneNumberSource {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SERVICE_CAPABILITY"},
+ value = {
+ SERVICE_CAPABILITY_VOICE,
+ SERVICE_CAPABILITY_SMS,
+ SERVICE_CAPABILITY_DATA,
+ })
+ public @interface ServiceCapability {
+ }
+
+ /**
+ * Represents a value indicating the voice calling capabilities of a subscription.
+ *
+ * <p>This value identifies whether the subscription supports various voice calling services.
+ * These services can include circuit-switched (CS) calling, packet-switched (PS) IMS (IP
+ * Multimedia Subsystem) calling, and over-the-top (OTT) calling options.
+ *
+ * <p>Note: The availability of emergency calling services is not solely dependent on this
+ * voice capability. Emergency services may be accessible even if the subscription lacks
+ * standard voice capabilities. However, the device's ability to support emergency calls
+ * can be influenced by its inherent voice capabilities, as determined by
+ * {@link TelephonyManager#isDeviceVoiceCapable()}.
+ *
+ * @see TelephonyManager#isDeviceVoiceCapable()
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_VOICE = 1;
+
+ /**
+ * Represents a value indicating the SMS capabilities of a subscription.
+ *
+ * <p>This value identifies whether the subscription supports various sms services.
+ * These services can include circuit-switched (CS) SMS, packet-switched (PS) IMS (IP
+ * Multimedia Subsystem) SMS, and over-the-top (OTT) SMS options.
+ *
+ * <p>Note: The availability of emergency SMS services is not solely dependent on this
+ * sms capability. Emergency services may be accessible even if the subscription lacks
+ * standard sms capabilities. However, the device's ability to support emergency sms
+ * can be influenced by its inherent sms capabilities, as determined by
+ * {@link TelephonyManager#isDeviceSmsCapable()}.
+ *
+ * @see TelephonyManager#isDeviceSmsCapable()
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_SMS = 2;
+
+ /**
+ * Represents a value indicating the data calling capabilities of a subscription.
+ */
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public static final int SERVICE_CAPABILITY_DATA = 3;
+
+ /**
+ * Maximum value of service capabilities supported so far.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA;
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_VOICE}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_VOICE_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE);
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_SMS}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_SMS_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS);
+
+ /**
+ * Bitmask for {@code SERVICE_CAPABILITY_DATA}.
+ * @hide
+ */
+ public static final int SERVICE_CAPABILITY_DATA_BITMASK =
+ serviceCapabilityToBitmask(SERVICE_CAPABILITY_DATA);
+
private final Context mContext;
/**
@@ -4484,4 +4574,38 @@ public class SubscriptionManager {
}
return new ArrayList<>();
}
+
+ /**
+ * @return the bitmasks combination of all service capabilities.
+ * @hide
+ */
+ public static int getAllServiceCapabilityBitmasks() {
+ return SERVICE_CAPABILITY_VOICE_BITMASK | SERVICE_CAPABILITY_SMS_BITMASK
+ | SERVICE_CAPABILITY_DATA_BITMASK;
+ }
+
+ /**
+ * @return The set of service capability from a bitmask combined one.
+ * @hide
+ */
+ @NonNull
+ @ServiceCapability
+ public static Set<Integer> getServiceCapabilitiesSet(int combinedServiceCapabilities) {
+ Set<Integer> capabilities = new HashSet<>();
+ for (int i = SERVICE_CAPABILITY_VOICE; i <= SERVICE_CAPABILITY_MAX; i++) {
+ final int capabilityBitmask = serviceCapabilityToBitmask(i);
+ if ((combinedServiceCapabilities & capabilityBitmask) == capabilityBitmask) {
+ capabilities.add(i);
+ }
+ }
+ return Collections.unmodifiableSet(capabilities);
+ }
+
+ /**
+ * @return The service capability bitmask from a {@link ServiceCapability} value.
+ * @hide
+ */
+ public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
+ return 1 << (capability - 1);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f8166e58cd2f..9e292be7f767 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2115,6 +2115,20 @@ public class TelephonyManager {
public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
"com.android.omadm.service.CONFIGURATION_UPDATE";
+ /**
+ * Activity action: Show setting to reset mobile networks.
+ *
+ * <p>On devices with a settings activity to reset mobile networks, the activity should be
+ * launched without additional permissions.
+ *
+ * <p>On some devices, this settings activity may not exist. Callers should ensure that this
+ * case is appropriately handled.
+ */
+ @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS =
+ "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
+
//
//
// Device Info
@@ -6683,8 +6697,10 @@ public class TelephonyManager {
* PackageManager.FEATURE_TELEPHONY system feature, which is available
* on any device with a telephony radio, even if the device is
* data-only.
+ * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ @Deprecated
public boolean isVoiceCapable() {
if (mContext == null) return true;
return mContext.getResources().getBoolean(
@@ -6692,6 +6708,30 @@ public class TelephonyManager {
}
/**
+ * @return true if the current device is "voice capable".
+ * <p>
+ * "Voice capable" means that this device supports circuit-switched or IMS packet switched
+ * (i.e. voice) phone calls over the telephony network, and is allowed to display the in-call
+ * UI while a cellular voice call is active. This will be false on "data only" devices which
+ * can't make voice calls and don't support any in-call UI.
+ * <p>
+ * Note: the meaning of this flag is subtly different from the PackageManager
+ * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
+ * radio, even if the device is data-only.
+ * <p>
+ * To check if a subscription is "voice capable", call method
+ * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with
+ * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ *
+ * @see SubscriptionInfo#getServiceCapabilities()
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public boolean isDeviceVoiceCapable() {
+ return isVoiceCapable();
+ }
+
+ /**
* @return true if the current device supports sms service.
* <p>
* If true, this means that the device supports both sending and
@@ -6699,6 +6739,7 @@ public class TelephonyManager {
* <p>
* Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
* disabled when device doesn't support sms.
+ * @deprecated Replaced by {@link #isDeviceSmsCapable()}
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public boolean isSmsCapable() {
@@ -6708,6 +6749,27 @@ public class TelephonyManager {
}
/**
+ * @return true if the current device supports SMS service.
+ * <p>
+ * If true, this means that the device supports both sending and
+ * receiving SMS via the telephony network.
+ * <p>
+ * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
+ * disabled when device doesn't support SMS.
+ * <p>
+ * To check if a subscription is "SMS capable", call method
+ * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
+ * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+ *
+ * @see SubscriptionInfo#getServiceCapabilities()
+ */
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
+ @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+ public boolean isDeviceSmsCapable() {
+ return isSmsCapable();
+ }
+
+ /**
* Requests all available cell information from all radios on the device including the
* camped/registered, serving, and neighboring cells.
*
diff --git a/tests/ActivityManagerPerfTests/tests/Android.bp b/tests/ActivityManagerPerfTests/tests/Android.bp
index e5813aec9f43..cce40f3a2664 100644
--- a/tests/ActivityManagerPerfTests/tests/Android.bp
+++ b/tests/ActivityManagerPerfTests/tests/Android.bp
@@ -30,6 +30,12 @@ android_test {
"ActivityManagerPerfTestsUtils",
"collector-device-lib-platform",
],
+ data: [
+ ":ActivityManagerPerfTestsTestApp",
+ ":ActivityManagerPerfTestsStubApp1",
+ ":ActivityManagerPerfTestsStubApp2",
+ ":ActivityManagerPerfTestsStubApp3",
+ ],
platform_apis: true,
min_sdk_version: "25",
// For android.permission.FORCE_STOP_PACKAGES permission
diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md
index 3455b0af4360..1a895dc7dfce 100644
--- a/tools/hoststubgen/README.md
+++ b/tools/hoststubgen/README.md
@@ -34,11 +34,6 @@ AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classe
- `test-tiny-framework/` See `README.md` in it.
- - `test-framework`
- This directory was used during the prototype phase, but now that we have real ravenwood tests,
- this directory is obsolete and should be deleted.
-
-
- `scripts`
- `dump-jar.sh`
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 4eac361d6e53..57bcc04a8aec 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -9,7 +9,7 @@ package {
// Visibility only for ravenwood prototype uses.
genrule_defaults {
- name: "hoststubgen-for-prototype-only-genrule",
+ name: "ravenwood-internal-only-visibility-genrule",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -19,7 +19,7 @@ genrule_defaults {
// Visibility only for ravenwood prototype uses.
java_defaults {
- name: "hoststubgen-for-prototype-only-java",
+ name: "ravenwood-internal-only-visibility-java",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -29,7 +29,7 @@ java_defaults {
// Visibility only for ravenwood prototype uses.
filegroup_defaults {
- name: "hoststubgen-for-prototype-only-filegroup",
+ name: "ravenwood-internal-only-visibility-filegroup",
visibility: [
":__subpackages__",
"//frameworks/base",
@@ -41,7 +41,7 @@ filegroup_defaults {
// This is only for the prototype. The productionized version is "ravenwood-annotations".
java_library {
name: "hoststubgen-annotations",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"annotations-src/**/*.java",
],
@@ -115,7 +115,7 @@ java_test_host {
// This is only for the prototype. The productionized version is "ravenwood-standard-options".
filegroup {
name: "hoststubgen-standard-options",
- defaults: ["hoststubgen-for-prototype-only-filegroup"],
+ defaults: ["ravenwood-internal-only-visibility-filegroup"],
srcs: [
"hoststubgen-standard-options.txt",
],
@@ -153,149 +153,25 @@ genrule_defaults {
],
}
-// Generate the stub/impl from framework-all, with hidden APIs.
-java_genrule_host {
- name: "framework-all-hidden-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-hidden-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-hidden-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// Generate the stub/impl from framework-all, with only public/system/test APIs, without
-// hidden APIs.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host",
- defaults: ["hoststubgen-command-defaults"],
- cmd: hoststubgen_common_options +
- "--intersect-stub-jar $(location :android_test_stubs_current{.jar}) " +
- "--in-jar $(location :framework-all) " +
- "--policy-override-file $(location framework-policy-override.txt) ",
- srcs: [
- ":framework-all",
- ":android_test_stubs_current{.jar}",
- "framework-policy-override.txt",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-stub",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_stub.jar}",
- ],
- out: [
- "host_stub.jar",
- ],
-}
-
-// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
-// This is only for the prototype. Do not use it in "productionized" build rules.
-java_genrule_host {
- name: "framework-all-test-api-host-impl",
- defaults: ["hoststubgen-for-prototype-only-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-all-test-api-host{host_impl.jar}",
- ],
- out: [
- "host_impl.jar",
- ],
-}
-
-// This library contains helper classes to build hostside tests/targets.
-// This essentially contains dependencies from tests that we can't actually use the real ones.
-// For example, the actual AndroidTestCase and AndroidJUnit4 don't run on the host side (yet),
-// so we pup "fake" implementations here.
-// Ideally this library should be empty.
-java_library_host {
- name: "hoststubgen-helper-framework-buildtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-buildtime-src/**/*.java",
- ],
- libs: [
- // We need it to pull in some of the framework classes used in this library,
- // such as Context.java.
- "framework-all-hidden-api-host-impl",
- "junit",
- ],
-}
-
-// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
-// that are needed at runtime.
-java_library_host {
- name: "hoststubgen-helper-framework-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
- srcs: [
- "helper-framework-runtime-src/**/*.java",
- ],
- exclude_srcs: [
- "helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java",
- ],
- libs: [
- "hoststubgen-helper-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
java_library_host {
name: "hoststubgen-helper-libcore-runtime",
- defaults: ["hoststubgen-for-prototype-only-java"],
srcs: [
"helper-framework-runtime-src/libcore-fake/**/*.java",
],
+ visibility: ["//visibility:private"],
}
java_host_for_device {
name: "hoststubgen-helper-libcore-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
libs: [
"hoststubgen-helper-libcore-runtime",
],
+ visibility: ["//visibility:private"],
}
java_library {
name: "hoststubgen-helper-framework-runtime.ravenwood",
- defaults: ["hoststubgen-for-prototype-only-java"],
+ defaults: ["ravenwood-internal-only-visibility-java"],
srcs: [
"helper-framework-runtime-src/framework/**/*.java",
],
@@ -308,88 +184,3 @@ java_library {
"hoststubgen-helper-libcore-runtime.ravenwood",
],
}
-
-// Defaults for host side test modules.
-// We need two rules for each test.
-// 1. A "-test-lib" jar, which compiles the test against the stub jar.
-// This one is only used by the second rule, so it should be "private.
-// 2. A "-test" jar, which includes 1 + the runtime (impl) jars.
-
-// This and next ones are for tests using framework-app, with hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-hidden-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-// Default rules to include `libandroid_runtime`. For now, it's empty, but we'll use it
-// once we start using JNI.
-java_defaults {
- name: "hosttest-with-libandroid_runtime",
- jni_libs: [
- // "libandroid_runtime",
-
- // TODO: Figure out how to build them automatically.
- // Following ones are depended by libandroid_runtime.
- // Without listing them here, not only we won't get them under
- // $ANDROID_HOST_OUT/testcases/*/lib64, but also not under
- // $ANDROID_HOST_OUT/lib64, so we'd fail to load them at runtime.
- // ($ANDROID_HOST_OUT/lib/ gets all of them though.)
- // "libcutils",
- // "libharfbuzz_ng",
- // "libminikin",
- // "libz",
- // "libbinder",
- // "libhidlbase",
- // "libvintf",
- // "libicu",
- // "libutils",
- // "libtinyxml2",
- ],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-hidden-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-hidden-api-host-impl",
- ],
-}
-
-// This and next ones are for tests using framework-app, with public/system/test APIs,
-// without hidden APIs.
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-lib-defaults",
- installable: false,
- libs: [
- "framework-all-test-api-host-stub",
- ],
- static_libs: [
- "hoststubgen-helper-framework-buildtime",
- "framework-annotations-lib",
- ],
- visibility: ["//visibility:private"],
-}
-
-java_defaults {
- name: "hosttest-with-framework-all-test-api-test-defaults",
- defaults: ["hosttest-with-libandroid_runtime"],
- installable: false,
- test_config: "AndroidTest-host.xml",
- static_libs: [
- "hoststubgen-helper-runtime",
- "hoststubgen-helper-framework-runtime",
- "framework-all-test-api-host-impl",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
deleted file mode 100644
index 51c5d9a05e52..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can never be null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value cannot be {@code null}.
- * @returnDoc This value cannot be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface NonNull {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
deleted file mode 100644
index f1f0e8b43f16..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.annotation;
-
-// [ravenwood] TODO: Find the actual androidx jar containing it.s
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a parameter, field or method return value can be null.
- * <p>
- * When decorating a method call parameter, this denotes that the parameter can
- * legitimately be null and the method will gracefully deal with it. Typically
- * used on optional parameters.
- * <p>
- * When decorating a method, this denotes the method might legitimately return
- * null.
- * <p>
- * This is a marker annotation and it has no specific attributes.
- *
- * @paramDoc This value may be {@code null}.
- * @returnDoc This value may be {@code null}.
- * @hide
- */
-@Retention(SOURCE)
-@Target({METHOD, PARAMETER, FIELD})
-public @interface Nullable {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
deleted file mode 100644
index 0c82e4e268d3..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.ext.junit.runners;
-
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.InitializationError;
-
-// TODO: We need to simulate the androidx test runner.
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/ext/junit/java/androidx/test/ext/junit/runners/AndroidJUnit4.java
-// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/runner/android_junit_runner/java/androidx/test/internal/runner/junit4/AndroidJUnit4ClassRunner.java
-
-public class AndroidJUnit4 extends BlockJUnit4ClassRunner {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
deleted file mode 100644
index 2470d8390f5d..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Designates a test as being flaky (non-deterministic).
- *
- * <p>Can then be used to filter tests on execution using -e annotation or -e notAnnotation as
- * desired.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface FlakyTest {
- /**
- * An optional bug number associated with the test. -1 Means that no bug number is associated with
- * the flaky annotation.
- *
- * @return int
- */
- int bugId() default -1;
-
- /**
- * Details, such as the reason of why the test is flaky.
- *
- * @return String
- */
- String detail() default "";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
deleted file mode 100644
index 578d7dc73647..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a large test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: &gt;1000ms
- *
- * <p>Large tests should be focused on testing integration of all application components. These
- * tests fully participate in the system and may make use of all resources such as databases, file
- * systems and network. As a rule of thumb most functional UI tests are large tests.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/LargeTest.html"><code>
- * android.test.suitebuilder.annotation.LargeTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface LargeTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
deleted file mode 100644
index dfdaa53ee6ac..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a medium test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: &lt;1000ms
- *
- * <p>Medium tests should be focused on a very limited subset of components or a single component.
- * Resource access to the file system through well defined interfaces like databases,
- * ContentProviders, or Context is permitted. Network access should be restricted, (long-running)
- * blocking operations should be avoided and use mock objects instead.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="{@docRoot}reference/android/test/suitebuilder/annotation/MediumTest.html"><code>
- * android.test.suitebuilder.annotation.MediumTest</code></a> and is the recommended way to annotate
- * tests written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface MediumTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
deleted file mode 100644
index 3d3ee3318bfa..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test should not be run on emulator.
- *
- * <p>It will be executed only if the test is running on the physical android device.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface RequiresDevice {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
deleted file mode 100644
index dd65ddb382dc..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Indicates that a specific test or class requires a minimum or maximum API Level to execute.
- *
- * <p>Test(s) will be skipped when executed on android platforms less/more than specified level
- * (inclusive).
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
-public @interface SdkSuppress {
- /** The minimum API level to execute (inclusive) */
- int minSdkVersion() default 1;
- /** The maximum API level to execute (inclusive) */
- int maxSdkVersion() default Integer.MAX_VALUE;
- /**
- * The {@link android.os.Build.VERSION.CODENAME} to execute on. This is intended to be used to run
- * on a pre-release SDK, where the {@link android.os.Build.VERSION.SDK_INT} has not yet been
- * finalized. This is treated as an OR operation with respect to the minSdkVersion and
- * maxSdkVersion attributes.
- *
- * <p>For example, to filter a test so it runs on only the prerelease R SDK: <code>
- * {@literal @}SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, codeName = "R")
- * </code>
- */
- String codeName() default "unset";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
deleted file mode 100644
index dd32df44effe..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to assign a small test size qualifier to a test. This annotation can be used at a
- * method or class level.
- *
- * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
- * test suite of similar run time.
- *
- * <p>Execution time: &lt;200ms
- *
- * <p>Small tests should be run very frequently. Focused on units of code to verify specific logical
- * conditions. These tests should runs in an isolated environment and use mock objects for external
- * dependencies. Resource access (such as file system, network, or databases) are not permitted.
- * Tests that interact with hardware, make binder calls, or that facilitate android instrumentation
- * should not use this annotation.
- *
- * <p>Note: This class replaces the deprecated Android platform size qualifier <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/SmallTest.html">
- * android.test.suitebuilder.annotation.SmallTest</a> and is the recommended way to annotate tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface SmallTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
deleted file mode 100644
index 88e636c2dd77..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.test.filters;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Use this annotation on test classes or test methods that should not be included in a test suite.
- * If the annotation appears on the class then no tests in that class will be included. If the
- * annotation appears only on a test method then only that method will be excluded.
- *
- * <p>Note: This class replaces the deprecated Android platform annotation <a
- * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/Suppress.html">
- * android.test.suitebuilder.annotation.Suppress</a> and is the recommended way to suppress tests
- * written with the AndroidX Test Library.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface Suppress {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
deleted file mode 100644
index e1379390f98b..000000000000
--- a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.test.runner;
-
-import org.junit.runners.model.InitializationError;
-
-public class AndroidJUnit4 extends androidx.test.ext.junit.runners.AndroidJUnit4 {
- public AndroidJUnit4(Class<?> testClass) throws InitializationError {
- super(testClass);
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-framework/Android.bp
deleted file mode 100644
index 2b91cc161b7f..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-build = ["AndroidHostTest.bp"]
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
deleted file mode 100644
index 1f8382ad468d..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Add `build = ["AndroidHostTest.bp"]` to Android.bp to include this file.
-
-// Compile the test jar, using 2 rules.
-// 1. Build the test against the stub.
-java_library_host {
- name: "HostStubGenTest-framework-test-host-test-lib",
- defaults: ["hosttest-with-framework-all-hidden-api-test-lib-defaults"],
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
-
- // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/
- "platform-test-annotations",
- "hoststubgen-annotations",
- ],
-}
-
-// 2. Link the above module with necessary runtime dependencies, so it can be executed stand-alone.
-java_test_host {
- name: "HostStubGenTest-framework-all-test-host-test",
- defaults: ["hosttest-with-framework-all-hidden-api-test-defaults"],
- static_libs: [
- "HostStubGenTest-framework-test-host-test-lib",
- ],
- test_suites: ["general-tests"],
-}
-
-// "Productionized" build rule.
-android_ravenwood_test {
- name: "HostStubGenTest-framework-test",
- srcs: [
- "src/**/*.java",
- ],
- static_libs: [
- "junit",
- "truth",
- "mockito",
- ],
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
deleted file mode 100644
index f35dcf69ecc9..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml -->
-<configuration description="CtsContentTestCases host-side test">
- <option name="test-suite-tag" value="ravenwood" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
- <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-
- <test class="com.android.tradefed.testtype.IsolatedHostTest" >
- <option name="jar" value="HostStubGenTest-framework-all-test-host-test.jar" />
- </test>
-</configuration>
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
deleted file mode 100644
index 26a9ad13f746..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/README.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# HostStubGen: (obsolete) real framework test
-
-This directory contains tests against the actual framework.jar code. The tests were
-copied from somewhere else in the android tree. We use this directory to quickly run existing
-tests.
-
-This directory was used during the prototype phase, but now that we have real ravenwood tests,
-this directory is obsolete and should be deleted.
-
-## How to run
-
-- With `atest`. This is the proper way to run it, but it may fail due to atest's known problems.
-
-```
-$ atest HostStubGenTest-framework-all-test-host-test
-```
-
-- Advanced option: `run-test-without-atest.sh` runs the test without using `atest`
-
-```
-$ ./run-test-without-atest.sh
-``` \ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
deleted file mode 100755
index cfc06a12e881..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/bash
-# 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.
-
-# Run HostStubGenTest-framework-test-host-test directly with JUnit.
-# (without using atest.)
-
-source "${0%/*}"/../../common.sh
-
-
-# Options:
-# -v enable verbose log
-# -d enable debugger
-
-verbose=0
-debug=0
-while getopts "vd" opt; do
- case "$opt" in
- v) verbose=1 ;;
- d) debug=1 ;;
- esac
-done
-shift $(($OPTIND - 1))
-
-
-if (( $verbose )) ; then
- JAVA_OPTS="$JAVA_OPTS -verbose:class"
-fi
-
-if (( $debug )) ; then
- JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700"
-fi
-
-#=======================================
-module=HostStubGenTest-framework-all-test-host-test
-module_jar=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/$module/$module.jar
-run m $module
-
-out=out
-
-rm -fr $out
-mkdir -p $out
-
-
-# Copy and extract the relevant jar files so we can look into them.
-run cp \
- $module_jar \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/*.jar \
- $out
-
-run extract $out/*.jar
-
-# Result is the number of failed tests.
-result=0
-
-
-# This suite runs all tests in the JAR.
-tests=(com.android.hoststubgen.hosthelper.HostTestSuite)
-
-# Uncomment this to run a specific test.
-# tests=(com.android.hoststubgen.frameworktest.LogTest)
-
-
-for class in ${tests[@]} ; do
- echo "Running $class ..."
-
- run cd "${module_jar%/*}"
- run $JAVA $JAVA_OPTS \
- -cp $module_jar \
- org.junit.runner.JUnitCore \
- $class || result=$(( $result + 1 ))
-done
-
-exit $result
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
deleted file mode 100644
index 2c5949c1c630..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.hoststubgen.frameworktest;
-
-// [ravewnwood] Copied from cts/, and commented out unsupported stuff.
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.util.ArrayMap;
-import android.util.Log;
-
-import org.junit.Test;
-
-import java.util.AbstractMap;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.function.BiFunction;
-
-/**
- * Some basic tests for {@link android.util.ArrayMap}.
- */
-public class ArrayMapTest {
- static final boolean DEBUG = false;
-
- private static boolean compare(Object v1, Object v2) {
- if (v1 == null) {
- return v2 == null;
- }
- if (v2 == null) {
- return false;
- }
- return v1.equals(v2);
- }
-
- private static void compareMaps(HashMap map, ArrayMap array) {
- if (map.size() != array.size()) {
- fail("Bad size: expected " + map.size() + ", got " + array.size());
- }
-
- Set<Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Object expValue = entry.getValue();
- Object gotValue = array.get(entry.getKey());
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + entry.getKey());
- }
- }
-
- for (int i = 0; i < array.size(); i++) {
- Object gotValue = array.valueAt(i);
- Object key = array.keyAt(i);
- Object expValue = map.get(key);
- if (!compare(expValue, gotValue)) {
- fail("Bad value: expected " + expValue + ", got " + gotValue
- + " at key " + key);
- }
- }
-
- if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
- fail("Entry set hash codes differ: map=0x"
- + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.entrySet().hashCode()));
- }
-
- if (!map.entrySet().equals(array.entrySet())) {
- fail("Failed calling equals on map entry set against array set");
- }
-
- if (!array.entrySet().equals(map.entrySet())) {
- fail("Failed calling equals on array entry set against map set");
- }
-
- if (map.keySet().hashCode() != array.keySet().hashCode()) {
- fail("Key set hash codes differ: map=0x"
- + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
- + Integer.toHexString(array.keySet().hashCode()));
- }
-
- if (!map.keySet().equals(array.keySet())) {
- fail("Failed calling equals on map key set against array set");
- }
-
- if (!array.keySet().equals(map.keySet())) {
- fail("Failed calling equals on array key set against map set");
- }
-
- if (!map.keySet().containsAll(array.keySet())) {
- fail("Failed map key set contains all of array key set");
- }
-
- if (!array.keySet().containsAll(map.keySet())) {
- fail("Failed array key set contains all of map key set");
- }
-
- if (!array.containsAll(map.keySet())) {
- fail("Failed array contains all of map key set");
- }
-
- if (!map.entrySet().containsAll(array.entrySet())) {
- fail("Failed map entry set contains all of array entry set");
- }
-
- if (!array.entrySet().containsAll(map.entrySet())) {
- fail("Failed array entry set contains all of map entry set");
- }
- }
-
- private static void validateArrayMap(ArrayMap array) {
- Set<Map.Entry> entrySet = array.entrySet();
- int index = 0;
- Iterator<Entry> entryIt = entrySet.iterator();
- while (entryIt.hasNext()) {
- Map.Entry entry = entryIt.next();
- Object value = entry.getKey();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- value = entry.getValue();
- realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map entry set: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Set keySet = array.keySet();
- Iterator keyIt = keySet.iterator();
- while (keyIt.hasNext()) {
- Object value = keyIt.next();
- Object realValue = array.keyAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map key set: expected key " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
-
- index = 0;
- Collection valueCol = array.values();
- Iterator valueIt = valueCol.iterator();
- while (valueIt.hasNext()) {
- Object value = valueIt.next();
- Object realValue = array.valueAt(index);
- if (!compare(realValue, value)) {
- fail("Bad array map value col: expected value " + realValue
- + ", got " + value + " at index " + index);
- }
- index++;
- }
- }
-
- private static void dump(Map map, ArrayMap array) {
- Log.e("test", "HashMap of " + map.size() + " entries:");
- Set<Map.Entry> mapSet = map.entrySet();
- for (Map.Entry entry : mapSet) {
- Log.e("test", " " + entry.getKey() + " -> " + entry.getValue());
- }
- Log.e("test", "ArrayMap of " + array.size() + " entries:");
- for (int i = 0; i < array.size(); i++) {
- Log.e("test", " " + array.keyAt(i) + " -> " + array.valueAt(i));
- }
- }
-
- private static void dump(ArrayMap map1, ArrayMap map2) {
- Log.e("test", "ArrayMap of " + map1.size() + " entries:");
- for (int i = 0; i < map1.size(); i++) {
- Log.e("test", " " + map1.keyAt(i) + " -> " + map1.valueAt(i));
- }
- Log.e("test", "ArrayMap of " + map2.size() + " entries:");
- for (int i = 0; i < map2.size(); i++) {
- Log.e("test", " " + map2.keyAt(i) + " -> " + map2.valueAt(i));
- }
- }
-
- @Test
- public void testCopyArrayMap() {
- // map copy constructor test
- ArrayMap newMap = new ArrayMap<Integer, String>();
- for (int i = 0; i < 10; ++i) {
- newMap.put(i, String.valueOf(i));
- }
- ArrayMap mapCopy = new ArrayMap(newMap);
- if (!compare(mapCopy, newMap)) {
- String msg = "ArrayMap copy constructor failure: expected " +
- newMap + ", got " + mapCopy;
- Log.e("test", msg);
- dump(newMap, mapCopy);
- fail(msg);
- return;
- }
- }
-
- @Test
- public void testEqualsArrayMap() {
- ArrayMap<Integer, String> map1 = new ArrayMap<>();
- ArrayMap<Integer, String> map2 = new ArrayMap<>();
- HashMap<Integer, String> map3 = new HashMap<>();
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for empty maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- for (int i = 0; i < 10; ++i) {
- String value = String.valueOf(i);
- map1.put(i, value);
- map2.put(i, value);
- map3.put(i, value);
- }
- if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
- fail("ArrayMap equals failure for populated maps " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.remove(0);
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map size " + map1 + ", " +
- map2 + ", " + map3);
- }
-
- map1.put(0, "-1");
- if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
- fail("ArrayMap equals failure for map contents " + map1 + ", " +
- map2 + ", " + map3);
- }
- }
-
- private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) {
- try {
- testMap.entrySet().toArray();
- fail();
- } catch (UnsupportedOperationException expected) {
- }
-
- try {
- Map.Entry<?, ?>[] entries = new Map.Entry[20];
- testMap.entrySet().toArray(entries);
- fail();
- } catch (UnsupportedOperationException expected) {
- }
- }
-
- // http://b/32294038, Test ArrayMap.entrySet().toArray()
- @Test
- public void testEntrySetArray() {
- // Create
- ArrayMap<Integer, String> testMap = new ArrayMap<>();
-
- // Test empty
- checkEntrySetToArray(testMap);
-
- // Test non-empty
- for (int i = 0; i < 10; ++i) {
- testMap.put(i, String.valueOf(i));
- }
- checkEntrySetToArray(testMap);
- }
-
- @Test
- public void testCanNotIteratePastEnd_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
- entryOf("key 1", "value 1"),
- entryOf("key 2", "value 2")
- ));
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- // Assert iteration over the expected two entries in any order
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> firstEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(firstEntry));
-
- assertTrue(iterator.hasNext());
- Map.Entry<String, String> secondEntry = copyOf(iterator.next());
- assertTrue(expectedEntriesToIterate.remove(secondEntry));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
- return new AbstractMap.SimpleEntry<>(key, value);
- }
-
- private static <K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
- return entryOf(entry.getKey(), entry.getValue());
- }
-
- @Test
- public void testCanNotIteratePastEnd_keySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
- Iterator<String> iterator = map.keySet().iterator();
-
- // Assert iteration over the expected two keys in any order
- assertTrue(iterator.hasNext());
- String firstKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(firstKey));
-
- assertTrue(iterator.hasNext());
- String secondKey = iterator.next();
- assertTrue(expectedKeysToIterate.remove(secondKey));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testCanNotIteratePastEnd_valuesIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
- Iterator<String> iterator = map.values().iterator();
-
- // Assert iteration over the expected two values in any order
- assertTrue(iterator.hasNext());
- String firstValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(firstValue));
-
- assertTrue(iterator.hasNext());
- String secondValue = iterator.next();
- assertTrue(expectedValuesToIterate.remove(secondValue));
-
- assertFalse(iterator.hasNext());
-
- try {
- iterator.next();
- fail();
- } catch (NoSuchElementException expected) {
- }
- }
-
- @Test
- public void testForEach() {
- ArrayMap<String, Integer> map = new ArrayMap<>();
-
- for (int i = 0; i < 50; ++i) {
- map.put(Integer.toString(i), i * 10);
- }
-
- // Make sure forEach goes through all of the elements.
- HashMap<String, Integer> seen = new HashMap<>();
- map.forEach(seen::put);
- compareMaps(seen, map);
- }
-
- /**
- * The entrySet Iterator returns itself from each call to {@code next()}. This is unusual
- * behavior for {@link Iterator#next()}; this test ensures that any future change to this
- * behavior is deliberate.
- */
- @Test
- public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
-
- assertSame(iterator, iterator.next());
- assertSame(iterator, iterator.next());
- }
-
- @SuppressWarnings("SelfEquals")
- @Test
- public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
- Map<String, String> map = new ArrayMap<>();
- map.put("key 1", "value 1");
- map.put("key 2", "value 2");
- Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
- iterator.next();
- iterator.remove();
- try {
- iterator.equals(iterator);
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- private static <T> void assertEqualsBothWays(T a, T b) {
- assertEquals(a, b);
- assertEquals(b, a);
- assertEquals(a.hashCode(), b.hashCode());
- }
-
- @Test
- public void testRemoveAll() {
- final ArrayMap<Integer, String> map = new ArrayMap<>();
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i.toString());
- }
-
- final ArrayMap<Integer, String> expectedMap = new ArrayMap<>();
- for (Integer i : Arrays.asList(2, 4)) {
- expectedMap.put(i, String.valueOf(i));
- }
- map.removeAll(Arrays.asList(0, 1, 3, 5, 6));
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map);
- }
-
- map.removeAll(Collections.emptyList());
- if (!compare(map, expectedMap)) {
- fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " +
- map);
- }
-
- map.removeAll(Arrays.asList(2, 4));
- if (!map.isEmpty()) {
- fail("ArrayMap removeAll failure, expect empty, but " + map);
- }
- }
-
- @Test
- public void testReplaceAll() {
- final ArrayMap<Integer, Integer> map = new ArrayMap<>();
- final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
- final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
- for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
- map.put(i, i);
- expectedMap.put(i, 2 * i);
- }
-
- map.replaceAll(function);
- if (!compare(map, expectedMap)) {
- fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
- }
- }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
deleted file mode 100644
index 3e33b54bb9f9..000000000000
--- a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.hoststubgen.frameworktest;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Some basic tests for {@link android.util.Log}.
- */
-public class LogTest {
- @Test
- public void testBasicLogging() {
- Log.v("TAG", "Test v log");
- Log.d("TAG", "Test d log");
- Log.i("TAG", "Test i log");
- Log.w("TAG", "Test w log");
- Log.e("TAG", "Test e log");
- }
-
- @Test
- public void testNativeMethods() {
- assertThat(Log.isLoggable("mytag", Log.INFO)).isTrue();
- }
-}
diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
deleted file mode 100755
index 72681234dad8..000000000000
--- a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-# 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.
-
-
-# Script to build `framework-host-stub` and `framework-host-impl`, and copy the
-# generated jars to $out, and unzip them. Useful for looking into the generated files.
-
-source "${0%/*}"/../common.sh
-
-out=framework-all-stub-out
-
-rm -fr $out
-mkdir -p $out
-
-# Build the jars with `m`.
-run m framework-all-hidden-api-host
-
-# Copy the jar to out/ and extract them.
-run cp \
- $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/* \
- $out
-
-extract $out/*.jar
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index 222c874ac34b..a6847ae97bae 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -22,7 +22,6 @@ ATEST_ARGS="--host"
# These tests are known to pass.
READY_TEST_MODULES=(
- HostStubGenTest-framework-all-test-host-test
hoststubgen-test-tiny-test
CtsUtilTestCasesRavenwood
CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too.
@@ -30,7 +29,6 @@ READY_TEST_MODULES=(
MUST_BUILD_MODULES=(
"${NOT_READY_TEST_MODULES[*]}"
- HostStubGenTest-framework-test
)
# First, build all the test / etc modules. This shouldn't fail.
@@ -44,11 +42,8 @@ run atest $ATEST_ARGS hoststubgentest hoststubgen-invoke-test
# files, and they may fail when something changes in the build system.
run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
-run ./hoststubgen/test-framework/run-test-without-atest.sh
-
run ./hoststubgen/test-tiny-framework/run-test-manually.sh
run atest $ATEST_ARGS tiny-framework-dump-test
-run ./scripts/build-framework-hostside-jars-and-extract.sh
# This script is already broken on goog/master
# run ./scripts/build-framework-hostside-jars-without-genrules.sh
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208db14ec..5cbb1aad1851 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@ data class EnforcePermissionFix(
.autoFix()
.build()
- return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+ return LintFix.create()
+ .name(annotateFix.getDisplayName())
+ .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
}
private val annotation: String