summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp13
-rw-r--r--MEMORY_OWNERS1
-rw-r--r--apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java4
-rw-r--r--api/api.go2
-rw-r--r--core/api/current.txt73
-rw-r--r--core/api/system-current.txt55
-rw-r--r--core/java/android/app/Activity.java6
-rw-r--r--core/java/android/app/ActivityManagerInternal.java6
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java29
-rw-r--r--core/java/android/app/AppOpsManager.aidl1
-rw-r--r--core/java/android/app/AppOpsManager.java246
-rw-r--r--core/java/android/app/AppOpsManagerInternal.java8
-rw-r--r--core/java/android/app/ApplicationPackageManager.java13
-rw-r--r--core/java/android/app/BackgroundStartPrivileges.java21
-rw-r--r--core/java/android/app/IUserSwitchObserver.aidl12
-rw-r--r--core/java/android/app/Notification.java69
-rw-r--r--core/java/android/app/NotificationManager.java10
-rw-r--r--core/java/android/app/SystemServiceRegistry.java9
-rw-r--r--core/java/android/app/UserSwitchObserver.java6
-rw-r--r--core/java/android/app/contextualsearch/ContextualSearchManager.java39
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig8
-rw-r--r--core/java/android/app/jank/AppJankStats.java126
-rw-r--r--core/java/android/app/jank/FrameOverrunHistogram.java107
-rw-r--r--core/java/android/app/jank/JankDataProcessor.java9
-rw-r--r--core/java/android/app/jank/RelativeFrameTimeHistogram.java129
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl15
-rw-r--r--core/java/android/content/pm/PackageManager.java19
-rw-r--r--core/java/android/content/pm/flags.aconfig10
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java9
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java7
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java18
-rw-r--r--core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java24
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java153
-rw-r--r--core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java105
-rw-r--r--core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java13
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java13
-rw-r--r--core/java/android/hardware/camera2/params/SharedSessionConfiguration.java2
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java34
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java (renamed from core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java)2
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java (renamed from core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java)2
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointMessageCallback.java (renamed from core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java)6
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSession.java2
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSessionResult.java2
-rw-r--r--core/java/android/hardware/contexthub/HubServiceInfo.java4
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java33
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java33
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java75
-rw-r--r--core/java/android/os/BaseBundle.java10
-rw-r--r--core/java/android/os/BinderProxy.java6
-rw-r--r--core/java/android/os/Parcel.java12
-rw-r--r--core/java/android/os/TestLooperManager.java16
-rw-r--r--core/java/android/os/UserManager.java26
-rw-r--r--core/java/android/provider/Settings.java16
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletService.java8
-rw-r--r--core/java/android/service/settings/preferences/GetValueRequest.java1
-rw-r--r--core/java/android/service/settings/preferences/GetValueResult.java1
-rw-r--r--core/java/android/service/settings/preferences/MetadataRequest.java1
-rw-r--r--core/java/android/service/settings/preferences/MetadataResult.java1
-rw-r--r--core/java/android/service/settings/preferences/SetValueRequest.java1
-rw-r--r--core/java/android/service/settings/preferences/SetValueResult.java1
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java20
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceValue.java1
-rw-r--r--core/java/android/tracing/flags.aconfig8
-rw-r--r--core/java/android/view/Choreographer.java35
-rw-r--r--core/java/android/view/Display.java4
-rw-r--r--core/java/android/view/DisplayInfo.java22
-rw-r--r--core/java/android/view/KeyCharacterMap.java13
-rw-r--r--core/java/android/view/Surface.java9
-rw-r--r--core/java/android/view/SurfaceControl.java9
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/android/view/ViewRootImpl.java9
-rw-r--r--core/java/android/view/contentcapture/ChildContentCaptureSession.java5
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureEvent.java11
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java31
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java9
-rw-r--r--core/java/android/view/contentcapture/OWNERS5
-rw-r--r--core/java/android/view/contentcapture/flags/content_capture_flags.aconfig7
-rw-r--r--core/java/android/window/WindowTokenClientController.java3
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig23
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig22
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig17
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl1
-rw-r--r--core/java/com/android/internal/app/ProcessMap.java5
-rw-r--r--core/java/com/android/internal/jank/DisplayResolutionTracker.java4
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl3
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java44
-rw-r--r--core/jni/android_hardware_camera2_CameraDevice.cpp11
-rw-r--r--core/jni/android_util_Binder.cpp6
-rw-r--r--core/res/AndroidManifest.xml14
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml40
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml7
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml6
-rw-r--r--core/res/res/layout/notification_2025_template_header.xml31
-rw-r--r--core/res/res/values-watch/config.xml3
-rw-r--r--core/res/res/values/config.xml11
-rw-r--r--core/res/res/values/config_watch.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/res/color/color_with_lstar.xml2
-rw-r--r--core/tests/coretests/res/values/colors.xml1
-rw-r--r--core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java11
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java208
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java3
-rw-r--r--core/tests/coretests/src/android/graphics/BitmapFactoryTest.java12
-rw-r--r--core/tests/coretests/src/android/graphics/BitmapTest.java53
-rw-r--r--core/tests/coretests/src/android/graphics/ColorStateListTest.java39
-rw-r--r--core/tests/coretests/src/android/graphics/FontFileUtilTest.java3
-rw-r--r--core/tests/coretests/src/android/graphics/PaintFontVariationTest.java5
-rw-r--r--core/tests/coretests/src/android/graphics/PaintTest.java44
-rw-r--r--core/tests/coretests/src/android/graphics/ThreadBitmapTest.java13
-rw-r--r--core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java35
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java18
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt86
-rw-r--r--core/tests/coretests/src/android/view/ViewFrameRateTest.java82
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java12
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java5
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java162
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/multivalentTests/Android.bp2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt9
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt51
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt20
-rw-r--r--libs/androidfw/Android.bp1
-rw-r--r--libs/androidfw/AssetManager.cpp32
-rw-r--r--libs/androidfw/TypeWrappers.cpp59
-rw-r--r--libs/androidfw/include/androidfw/AssetManager.h22
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h5
-rw-r--r--libs/androidfw/include/androidfw/TypeWrappers.h30
-rw-r--r--libs/androidfw/include/androidfw/misc.h35
-rw-r--r--libs/androidfw/misc.cpp57
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp11
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp127
-rw-r--r--libs/protoutil/Android.bp1
-rw-r--r--location/api/system-current.txt86
-rw-r--r--location/java/android/location/AuxiliaryInformation.java274
-rw-r--r--location/java/android/location/BeidouAssistance.java36
-rw-r--r--location/java/android/location/GalileoAssistance.java36
-rw-r--r--location/java/android/location/GlonassAssistance.java39
-rw-r--r--location/java/android/location/GpsAssistance.java45
-rw-r--r--location/java/android/location/QzssAssistance.java62
-rw-r--r--location/java/android/location/RealTimeIntegrityModel.java77
-rw-r--r--location/java/android/location/flags/location.aconfig2
-rw-r--r--location/java/android/location/provider/GnssAssistanceProviderBase.java152
-rw-r--r--location/java/android/location/provider/IGnssAssistanceCallback.aidl (renamed from core/tests/coretests/src/android/graphics/GraphicsTests.java)20
-rw-r--r--location/java/android/location/provider/IGnssAssistanceProvider.aidl28
-rw-r--r--media/java/android/media/FadeManagerConfiguration.java1
-rw-r--r--media/java/android/media/MediaCodecInfo.java5716
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java75
-rw-r--r--media/jni/Android.bp2
-rw-r--r--media/jni/android_media_CodecCapabilities.cpp1015
-rw-r--r--media/jni/android_media_CodecCapabilities.h47
-rw-r--r--media/jni/android_media_MediaCodec.cpp39
-rw-r--r--media/jni/android_media_MediaCodecList.cpp154
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
-rw-r--r--nfc/tests/src/android/nfc/NfcManagerTest.java71
-rw-r--r--nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java113
-rw-r--r--nfc/tests/src/android/nfc/tech/NfcATest.java185
-rw-r--r--nfc/tests/src/android/nfc/tech/NfcBTest.java119
-rw-r--r--nfc/tests/src/android/nfc/tech/NfcFTest.java191
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java47
-rw-r--r--packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java47
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml1
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java17
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt6
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt7
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt7
-rw-r--r--packages/SettingsLib/Graph/graph.proto6
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt22
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt6
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt40
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java1
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java155
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt3
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java23
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig12
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java170
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java527
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java3
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java315
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java27
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java22
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java37
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java61
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java28
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java41
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java4
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java58
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java4
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java175
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/Android.bp4
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig22
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java57
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt81
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt17
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt37
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt20
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt3
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt19
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt31
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt10
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt4
-rw-r--r--packages/SystemUI/docs/clock-plugins.md6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java221
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java91
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model)8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt)73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt122
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt)86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java134
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt184
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt190
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java1
-rw-r--r--packages/SystemUI/res/color/slider_active_track_color.xml19
-rw-r--r--packages/SystemUI/res/color/slider_inactive_track_color.xml19
-rw-r--r--packages/SystemUI/res/color/slider_thumb_color.xml20
-rw-r--r--packages/SystemUI/res/drawable/audio_bars_idle.xml56
-rw-r--r--packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml17
-rw-r--r--packages/SystemUI/res/drawable/ic_hearing_device_expand.xml27
-rw-r--r--packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml67
-rw-r--r--packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml46
-rw-r--r--packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml11
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml8
-rw-r--r--packages/SystemUI/res/raw/audio_bars_in.json1
-rw-r--r--packages/SystemUI/res/raw/audio_bars_out.json1
-rw-r--r--packages/SystemUI/res/raw/audio_bars_playing.json1
-rw-r--r--packages/SystemUI/res/values-night/colors.xml3
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml9
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml24
-rw-r--r--packages/SystemUI/res/values/styles.xml13
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt37
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt55
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java67
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt62
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java322
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt225
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java154
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt150
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt197
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt)26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt138
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java8
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt58
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java16
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java7
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java36
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java25
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java18
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java16
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java13
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java2
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/am/UserController.java174
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java101
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java10
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java4
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java102
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java17
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java38
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java97
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2Provider.java37
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java135
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java64
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java36
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java502
-rw-r--r--services/core/java/com/android/server/notification/NotificationDelegate.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java37
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java8
-rw-r--r--services/core/java/com/android/server/pm/Computer.java14
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java32
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java12
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java9
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/power/Notifier.java28
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java135
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java13
-rw-r--r--services/core/java/com/android/server/storage/CacheQuotaStrategy.java5
-rw-r--r--services/core/java/com/android/server/wm/AbsAppSnapshotController.java157
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java63
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java12
-rw-r--r--services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java7
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java8
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java18
-rw-r--r--services/core/java/com/android/server/wm/CameraStateMonitor.java6
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java8
-rw-r--r--services/core/java/com/android/server/wm/DragInputEventReceiver.java6
-rw-r--r--services/core/java/com/android/server/wm/DragState.java181
-rw-r--r--services/core/java/com/android/server/wm/FadeAnimationController.java26
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java6
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java1
-rw-r--r--services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java19
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java77
-rw-r--r--services/core/java/com/android/server/wm/Transition.java15
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/java/com/android/server/SystemServer.java11
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt13
-rw-r--r--services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java33
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt41
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java16
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java85
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java92
-rw-r--r--services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java20
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java141
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java37
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java8
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java3
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java3
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankUtils.java18
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/TestWidget.java8
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java2
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java3
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt23
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt4
-rw-r--r--tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt41
-rw-r--r--tools/aapt/Package.cpp11
-rw-r--r--tools/aapt2/Debug.cpp24
597 files changed, 17954 insertions, 6739 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2f843f9d6164..8547ec164084 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1809,12 +1809,25 @@ aconfig_declarations {
name: "aconfig_settingslib_flags",
package: "com.android.settingslib.flags",
container: "system",
+ exportable: true,
srcs: [
"packages/SettingsLib/aconfig/settingslib.aconfig",
],
}
java_aconfig_library {
+ name: "aconfig_settingslib_exported_flags_java_lib",
+ aconfig_declarations: "aconfig_settingslib_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
+}
+
+java_aconfig_library {
name: "aconfig_settingslib_flags_java_lib",
aconfig_declarations: "aconfig_settingslib_flags",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS
index 89ce5140d8ea..12aa2951bbc9 100644
--- a/MEMORY_OWNERS
+++ b/MEMORY_OWNERS
@@ -2,5 +2,4 @@ surenb@google.com
tjmercier@google.com
kaleshsingh@google.com
jyescas@google.com
-carlosgalo@google.com
jji@google.com
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index c77528021201..ed669beae1ce 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -63,12 +63,14 @@ public class ZipFilePerfTest {
@Test
@Parameters(method = "getData")
- public void timeZipFileOpenClose(int numEntries) throws Exception {
+ public void timeZipFileOpen(int numEntries) throws Exception {
setUp(numEntries);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
ZipFile zf = new ZipFile(mFile);
+ state.pauseTiming();
zf.close();
+ state.resumeTiming();
}
}
diff --git a/api/api.go b/api/api.go
index e4d783eba4c3..cbdb7e81ab86 100644
--- a/api/api.go
+++ b/api/api.go
@@ -105,7 +105,7 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
ctx.WalkDeps(func(child, parent android.Module) bool {
- if _, ok := child.(java.AndroidLibraryDependency); ok && child.Name() != "framework-res" {
+ if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
// Stubs of BCP and SSCP libraries should not have any dependencies on apps
// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
ctx.ModuleErrorf(
diff --git a/core/api/current.txt b/core/api/current.txt
index 32507dfef9b6..7c56a5811abb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9190,37 +9190,37 @@ package android.app.blob {
package android.app.jank {
@FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats {
- ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.FrameOverrunHistogram);
- method @NonNull public android.app.jank.FrameOverrunHistogram getFrameOverrunHistogram();
+ ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram);
method public long getJankyFrameCount();
+ method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram();
method public long getTotalFrameCount();
method public int getUid();
method @NonNull public String getWidgetCategory();
method @NonNull public String getWidgetId();
method @NonNull public String getWidgetState();
- field public static final String ANIMATING = "animating";
- field public static final String ANIMATION = "animation";
- field public static final String DRAGGING = "dragging";
- field public static final String FLINGING = "flinging";
- field public static final String KEYBOARD = "keyboard";
- field public static final String MEDIA = "media";
- field public static final String NAVIGATION = "navigation";
- field public static final String NONE = "none";
- field public static final String OTHER = "other";
- field public static final String PLAYBACK = "playback";
- field public static final String PREDICTIVE_BACK = "predictive_back";
- field public static final String SCROLL = "scroll";
- field public static final String SCROLLING = "scrolling";
- field public static final String SWIPING = "swiping";
- field public static final String TAPPING = "tapping";
- field public static final String WIDGET_CATEGORY_UNSPECIFIED = "widget_category_unspecified";
- field public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified";
- field public static final String ZOOMING = "zooming";
- }
-
- @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class FrameOverrunHistogram {
- ctor public FrameOverrunHistogram();
- method public void addFrameOverrunMillis(int);
+ field public static final String WIDGET_CATEGORY_ANIMATION = "animation";
+ field public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard";
+ field public static final String WIDGET_CATEGORY_MEDIA = "media";
+ field public static final String WIDGET_CATEGORY_NAVIGATION = "navigation";
+ field public static final String WIDGET_CATEGORY_OTHER = "other";
+ field public static final String WIDGET_CATEGORY_SCROLL = "scroll";
+ field public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified";
+ field public static final String WIDGET_STATE_ANIMATING = "animating";
+ field public static final String WIDGET_STATE_DRAGGING = "dragging";
+ field public static final String WIDGET_STATE_FLINGING = "flinging";
+ field public static final String WIDGET_STATE_NONE = "none";
+ field public static final String WIDGET_STATE_PLAYBACK = "playback";
+ field public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back";
+ field public static final String WIDGET_STATE_SCROLLING = "scrolling";
+ field public static final String WIDGET_STATE_SWIPING = "swiping";
+ field public static final String WIDGET_STATE_TAPPING = "tapping";
+ field public static final String WIDGET_STATE_UNSPECIFIED = "unspecified";
+ field public static final String WIDGET_STATE_ZOOMING = "zooming";
+ }
+
+ @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public class RelativeFrameTimeHistogram {
+ ctor public RelativeFrameTimeHistogram();
+ method public void addRelativeFrameTimeMillis(int);
method @NonNull public int[] getBucketCounters();
method @NonNull public int[] getBucketEndpointsMillis();
}
@@ -42521,7 +42521,7 @@ package android.service.settings.preferences {
field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR;
}
- public static final class GetValueRequest.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueRequest.Builder {
ctor public GetValueRequest.Builder(@NonNull String, @NonNull String);
method @NonNull public android.service.settings.preferences.GetValueRequest build();
}
@@ -42542,7 +42542,7 @@ package android.service.settings.preferences {
field public static final int RESULT_UNSUPPORTED = 1; // 0x1
}
- public static final class GetValueResult.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class GetValueResult.Builder {
ctor public GetValueResult.Builder(int);
method @NonNull public android.service.settings.preferences.GetValueResult build();
method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata);
@@ -42555,7 +42555,7 @@ package android.service.settings.preferences {
field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR;
}
- public static final class MetadataRequest.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataRequest.Builder {
ctor public MetadataRequest.Builder();
method @NonNull public android.service.settings.preferences.MetadataRequest build();
}
@@ -42571,7 +42571,7 @@ package android.service.settings.preferences {
field public static final int RESULT_UNSUPPORTED = 1; // 0x1
}
- public static final class MetadataResult.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class MetadataResult.Builder {
ctor public MetadataResult.Builder(int);
method @NonNull public android.service.settings.preferences.MetadataResult build();
method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>);
@@ -42586,7 +42586,7 @@ package android.service.settings.preferences {
field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR;
}
- public static final class SetValueRequest.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueRequest.Builder {
ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue);
method @NonNull public android.service.settings.preferences.SetValueRequest build();
}
@@ -42608,14 +42608,13 @@ package android.service.settings.preferences {
field public static final int RESULT_UNSUPPORTED = 1; // 0x1
}
- public static final class SetValueResult.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SetValueResult.Builder {
ctor public SetValueResult.Builder(int);
method @NonNull public android.service.settings.preferences.SetValueResult build();
}
@FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public java.util.List<java.lang.String> getBreadcrumbs();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getKey();
method @Nullable public android.content.Intent getLaunchIntent();
@@ -42631,17 +42630,16 @@ package android.service.settings.preferences {
method public boolean isWritable();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR;
+ field public static final int DEEPLINK_ONLY = 2; // 0x2
field public static final int EXPECT_POST_CONFIRMATION = 1; // 0x1
- field public static final int EXPECT_PRE_CONFIRMATION = 2; // 0x2
field public static final int NO_DIRECT_ACCESS = 3; // 0x3
field public static final int NO_SENSITIVITY = 0; // 0x0
}
- public static final class SettingsPreferenceMetadata.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceMetadata.Builder {
ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String);
method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build();
method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean);
- method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean);
method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.content.Intent);
@@ -42688,7 +42686,7 @@ package android.service.settings.preferences {
field public static final int TYPE_STRING = 3; // 0x3
}
- public static final class SettingsPreferenceValue.Builder {
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final class SettingsPreferenceValue.Builder {
ctor public SettingsPreferenceValue.Builder(int);
method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build();
method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean);
@@ -53484,9 +53482,9 @@ package android.view {
field public static final int CHANGE_FRAME_RATE_ALWAYS = 1; // 0x1
field public static final int CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0; // 0x0
field @NonNull public static final android.os.Parcelable.Creator<android.view.Surface> CREATOR;
+ field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2; // 0x2
field public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0; // 0x0
field public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; // 0x1
- field @FlaggedApi("com.android.graphics.surfaceflinger.flags.arr_setframerate_gte_enum") public static final int FRAME_RATE_COMPATIBILITY_GTE = 2; // 0x2
field public static final int ROTATION_0 = 0; // 0x0
field public static final int ROTATION_180 = 2; // 0x2
field public static final int ROTATION_270 = 3; // 0x3
@@ -57120,6 +57118,7 @@ package android.view.contentcapture {
method public void close();
method @NonNull public final android.view.contentcapture.ContentCaptureSession createContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext);
method public final void destroy();
+ method @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public void flush();
method @Nullable public final android.view.contentcapture.ContentCaptureContext getContentCaptureContext();
method @NonNull public final android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6d24e4684e77..eca8eddc918e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -360,6 +360,7 @@ package android {
field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+ field @FlaggedApi("android.content.pm.uid_based_provider_lookup") public static final String RESOLVE_COMPONENT_FOR_UID = "android.permission.RESOLVE_COMPONENT_FOR_UID";
field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM";
field @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled") public static final String RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS = "android.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS";
field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
@@ -4241,6 +4242,7 @@ package android.content.pm {
method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void replacePreferredActivity(@NonNull android.content.IntentFilter, int, @NonNull java.util.List<android.content.ComponentName>, @NonNull android.content.ComponentName);
+ method @FlaggedApi("android.content.pm.uid_based_provider_lookup") @Nullable @RequiresPermission(android.Manifest.permission.RESOLVE_COMPONENT_FOR_UID) public android.content.pm.ProviderInfo resolveContentProviderForUid(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags, int);
method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
method public void sendDeviceCustomizationReadyBroadcast();
@@ -5079,8 +5081,8 @@ package android.hardware.contexthub {
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpoint {
- method @Nullable public android.hardware.contexthub.IHubEndpointLifecycleCallback getLifecycleCallback();
- method @Nullable public android.hardware.contexthub.IHubEndpointMessageCallback getMessageCallback();
+ method @Nullable public android.hardware.contexthub.HubEndpointLifecycleCallback getLifecycleCallback();
+ method @Nullable public android.hardware.contexthub.HubEndpointMessageCallback getMessageCallback();
method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection();
method @Nullable public String getTag();
method public int getVersion();
@@ -5095,14 +5097,19 @@ package android.hardware.contexthub {
public static final class HubEndpoint.Builder {
ctor public HubEndpoint.Builder(@NonNull android.content.Context);
method @NonNull public android.hardware.contexthub.HubEndpoint build();
- method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
- method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointLifecycleCallback);
- method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
- method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.IHubEndpointMessageCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull android.hardware.contexthub.HubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setLifecycleCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointLifecycleCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull android.hardware.contexthub.HubEndpointMessageCallback);
+ method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointMessageCallback);
method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setServiceInfoCollection(@NonNull java.util.Collection<android.hardware.contexthub.HubServiceInfo>);
method @NonNull public android.hardware.contexthub.HubEndpoint.Builder setTag(@NonNull String);
}
+ @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointDiscoveryCallback {
+ method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
+ method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
+ }
+
@FlaggedApi("android.chre.flags.offload_api") public final class HubEndpointInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier getIdentifier();
@@ -5126,6 +5133,16 @@ package android.hardware.contexthub {
method public long getHub();
}
+ @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointLifecycleCallback {
+ method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
+ method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
+ }
+
+ @FlaggedApi("android.chre.flags.offload_api") public interface HubEndpointMessageCallback {
+ method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage);
+ }
+
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
method @Nullable public String getServiceDescriptor();
@@ -5174,21 +5191,6 @@ package android.hardware.contexthub {
method @NonNull public android.hardware.contexthub.HubServiceInfo build();
}
- @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback {
- method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
- method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
- }
-
- @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
- method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
- method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
- method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
- }
-
- @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
- method public void onMessageReceived(@NonNull android.hardware.contexthub.HubEndpointSession, @NonNull android.hardware.contexthub.HubMessage);
- }
-
}
package android.hardware.devicestate {
@@ -6192,16 +6194,16 @@ package android.hardware.location {
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, long);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback, @NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.HubEndpointDiscoveryCallback);
field public static final int AUTHORIZATION_DENIED = 0; // 0x0
field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
@@ -19091,6 +19093,7 @@ package android.view.contentcapture {
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR;
field public static final int TYPE_CONTEXT_UPDATED = 6; // 0x6
+ field @FlaggedApi("android.view.contentcapture.flags.ccapi_baklava_enabled") public static final int TYPE_SESSION_FLUSH = 11; // 0xb
field public static final int TYPE_SESSION_PAUSED = 8; // 0x8
field public static final int TYPE_SESSION_RESUMED = 7; // 0x7
field public static final int TYPE_VIEW_APPEARED = 1; // 0x1
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fee8cdb1ce51..c3ef104075f2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5834,7 +5834,11 @@ public class Activity extends ContextThemeWrapper
final int size = permissions.length;
int[] results = new int[size];
for (int i = 0; i < size; i++) {
- results[i] = deviceContext.getPermissionRequestState(permissions[i]);
+ if (permissions[i] == null) {
+ results[i] = Context.PERMISSION_REQUEST_STATE_UNREQUESTABLE;
+ } else {
+ results[i] = deviceContext.getPermissionRequestState(permissions[i]);
+ }
}
return results;
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index abdfb53537f8..999db18a1229 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -485,6 +485,11 @@ public abstract class ActivityManagerInternal {
*/
public static final int OOM_ADJ_REASON_FOLLOW_UP = 23;
+ /**
+ * Oom Adj Reason: Update after oom adjuster configuration has changed.
+ */
+ public static final int OOM_ADJ_REASON_RECONFIGURATION = 24;
+
@IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
OOM_ADJ_REASON_NONE,
OOM_ADJ_REASON_ACTIVITY,
@@ -510,6 +515,7 @@ public abstract class ActivityManagerInternal {
OOM_ADJ_REASON_RESTRICTION_CHANGE,
OOM_ADJ_REASON_COMPONENT_DISABLED,
OOM_ADJ_REASON_FOLLOW_UP,
+ OOM_ADJ_REASON_RECONFIGURATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OomAdjReason {}
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 61b56877589b..599f1a8be233 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -27,6 +27,7 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Stores App Compat information about a particular Task.
@@ -58,16 +59,11 @@ public class AppCompatTaskInfo implements Parcelable {
public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET;
/**
- * Contains the current app height of the letterboxed activity if available or
- * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+ * Contains the app bounds of the top activity or size compat mode
+ * bounds when in size compat mode. If null, contains bounds.
*/
- public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET;
-
- /**
- * Contains the current app width of the letterboxed activity if available or
- * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
- */
- public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
+ @NonNull
+ public final Rect topActivityAppBounds = new Rect();
/**
* Contains the top activity bounds when the activity is letterboxed.
@@ -350,8 +346,7 @@ public class AppCompatTaskInfo implements Parcelable {
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
- && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
- && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
+ && topActivityAppBounds.equals(that.topActivityAppBounds)
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
&& cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo);
@@ -371,8 +366,7 @@ public class AppCompatTaskInfo implements Parcelable {
== that.topActivityLetterboxHorizontalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
- && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
- && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
+ && topActivityAppBounds.equals(that.topActivityAppBounds)
&& cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo);
}
@@ -385,8 +379,7 @@ public class AppCompatTaskInfo implements Parcelable {
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
- topActivityLetterboxAppWidth = source.readInt();
- topActivityLetterboxAppHeight = source.readInt();
+ topActivityAppBounds.set(Objects.requireNonNull(source.readTypedObject(Rect.CREATOR)));
topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR);
cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
}
@@ -401,8 +394,7 @@ public class AppCompatTaskInfo implements Parcelable {
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
- dest.writeInt(topActivityLetterboxAppWidth);
- dest.writeInt(topActivityLetterboxAppHeight);
+ dest.writeTypedObject(topActivityAppBounds, flags);
dest.writeTypedObject(topActivityLetterboxBounds, flags);
dest.writeTypedObject(cameraCompatTaskInfo, flags);
}
@@ -421,8 +413,7 @@ public class AppCompatTaskInfo implements Parcelable {
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
- + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth
- + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
+ + " topActivityAppBounds=" + topActivityAppBounds
+ " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
+ " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
+ " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index b4dee2e937cb..56ed290baf2e 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -19,6 +19,7 @@ package android.app;
parcelable AppOpsManager.PackageOps;
parcelable AppOpsManager.NoteOpEventProxyInfo;
parcelable AppOpsManager.NoteOpEvent;
+parcelable AppOpsManager.NotedOp;
parcelable AppOpsManager.OpFeatureEntry;
parcelable AppOpsManager.OpEntry;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 19138126698c..53b4b54e9f93 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -262,6 +262,23 @@ public class AppOpsManager {
private static final Object sLock = new Object();
+ // A map that records noted times for each op.
+ private static ArrayMap<NotedOp, Integer> sPendingNotedOps = new ArrayMap<>();
+ private static HandlerThread sHandlerThread;
+ private static final int NOTE_OP_BATCHING_DELAY_MILLIS = 1000;
+
+ private boolean isNoteOpBatchingSupported() {
+ // If noteOp is called from system server no IPC is made, hence we don't need batching.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ return false;
+ }
+ return Flags.noteOpBatchingEnabled();
+ }
+
+ private static final Object sBatchedNoteOpLock = new Object();
+ @GuardedBy("sBatchedNoteOpLock")
+ private static boolean sIsBatchedNoteOpCallScheduled = false;
+
/** Current {@link OnOpNotedCallback}. Change via {@link #setOnOpNotedCallback} */
@GuardedBy("sLock")
private static @Nullable OnOpNotedCallback sOnOpNotedCallback;
@@ -7466,6 +7483,141 @@ public class AppOpsManager {
}
/**
+ * A NotedOp is an app op grouped in noteOp API and sent to the system server in a batch
+ *
+ * @hide
+ */
+ public static final class NotedOp implements Parcelable {
+ private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+ private final @IntRange(from = 0) int mUid;
+ private final @Nullable String mPackageName;
+ private final @Nullable String mAttributionTag;
+ private final int mVirtualDeviceId;
+ private final @Nullable String mMessage;
+ private final boolean mShouldCollectAsyncNotedOp;
+ private final boolean mShouldCollectMessage;
+
+ public NotedOp(int op, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+ boolean shouldCollectAsyncNotedOp, boolean shouldCollectMessage) {
+ mOp = op;
+ mUid = uid;
+ mPackageName = packageName;
+ mAttributionTag = attributionTag;
+ mVirtualDeviceId = virtualDeviceId;
+ mMessage = message;
+ mShouldCollectAsyncNotedOp = shouldCollectAsyncNotedOp;
+ mShouldCollectMessage = shouldCollectMessage;
+ }
+
+ NotedOp(Parcel source) {
+ mOp = source.readInt();
+ mUid = source.readInt();
+ mPackageName = source.readString();
+ mAttributionTag = source.readString();
+ mVirtualDeviceId = source.readInt();
+ mMessage = source.readString();
+ mShouldCollectAsyncNotedOp = source.readBoolean();
+ mShouldCollectMessage = source.readBoolean();
+ }
+
+ public int getOp() {
+ return mOp;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ public @Nullable String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ public int getVirtualDeviceId() {
+ return mVirtualDeviceId;
+ }
+
+ public @Nullable String getMessage() {
+ return mMessage;
+ }
+
+ public boolean getShouldCollectAsyncNotedOp() {
+ return mShouldCollectAsyncNotedOp;
+ }
+
+ public boolean getShouldCollectMessage() {
+ return mShouldCollectMessage;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOp);
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeString(mAttributionTag);
+ dest.writeInt(mVirtualDeviceId);
+ dest.writeString(mMessage);
+ dest.writeBoolean(mShouldCollectAsyncNotedOp);
+ dest.writeBoolean(mShouldCollectMessage);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NotedOp that = (NotedOp) o;
+ return mOp == that.mOp
+ && mUid == that.mUid
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mAttributionTag, that.mAttributionTag)
+ && mVirtualDeviceId == that.mVirtualDeviceId
+ && Objects.equals(mMessage, that.mMessage)
+ && Objects.equals(mShouldCollectAsyncNotedOp, that.mShouldCollectAsyncNotedOp)
+ && Objects.equals(mShouldCollectMessage, that.mShouldCollectMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mOp, mUid, mPackageName, mAttributionTag, mVirtualDeviceId,
+ mMessage, mShouldCollectAsyncNotedOp, mShouldCollectMessage);
+ }
+
+ @Override
+ public String toString() {
+ return "NotedOp{"
+ + "mOp=" + mOp
+ + ", mUid=" + mUid
+ + ", mPackageName=" + mPackageName
+ + ", mAttributionTag=" + mAttributionTag
+ + ", mVirtualDeviceId=" + mVirtualDeviceId
+ + ", mMessage=" + mMessage
+ + ", mShouldCollectAsyncNotedOp=" + mShouldCollectAsyncNotedOp
+ + ", mShouldCollectMessage=" + mShouldCollectMessage
+ + "}";
+ }
+
+ public static final @NonNull Creator<NotedOp> CREATOR =
+ new Creator<>() {
+ @Override public NotedOp createFromParcel(Parcel source) {
+ return new NotedOp(source);
+ }
+
+ @Override public NotedOp[] newArray(int size) {
+ return new NotedOp[size];
+ }
+ };
+ }
+
+ /**
* Computes the sum of the counts for the given flags in between the begin and
* end UID states.
*
@@ -9301,6 +9453,65 @@ public class AppOpsManager {
message);
}
+ /**
+ * Create a new NotedOp object to represent the note operation. If the note operation is
+ * a duplicate in the buffer, put it in a batch for an async binder call to the system server.
+ *
+ * @return whether this note operation is a duplicate in the buffer. If it's the
+ * first, the noteOp is not batched, the caller should manually call noteOperation.
+ */
+ private boolean batchDuplicateNoteOps(int op, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, @Nullable String message,
+ boolean collectAsync, boolean shouldCollectMessage) {
+ synchronized (sBatchedNoteOpLock) {
+ NotedOp notedOp = new NotedOp(op, uid, packageName, attributionTag,
+ virtualDeviceId, message, collectAsync, shouldCollectMessage);
+
+ // Batch same noteOp calls and send them with their counters to the system
+ // service asynchronously. The time window for batching is specified in
+ // NOTE_OP_BATCHING_DELAY_MILLIS. Always allow the first noteOp call to go
+ // through binder API. Accumulate subsequent same noteOp calls during the
+ // time window in sPendingNotedOps.
+ boolean isDuplicated = sPendingNotedOps.containsKey(notedOp);
+ if (!isDuplicated) {
+ sPendingNotedOps.put(notedOp, 0);
+ } else {
+ sPendingNotedOps.merge(notedOp, 1, Integer::sum);
+ }
+
+ if (!sIsBatchedNoteOpCallScheduled) {
+ if (sHandlerThread == null) {
+ sHandlerThread = new HandlerThread("AppOpsManagerNoteOpBatching");
+ sHandlerThread.start();
+ }
+
+ sHandlerThread.getThreadHandler().postDelayed(() -> {
+ ArrayMap<NotedOp, Integer> pendingNotedOpsCopy;
+ synchronized(sBatchedNoteOpLock) {
+ sIsBatchedNoteOpCallScheduled = false;
+ pendingNotedOpsCopy = sPendingNotedOps;
+ sPendingNotedOps = new ArrayMap<>();
+ }
+ for (int i = pendingNotedOpsCopy.size() - 1; i >= 0; i--) {
+ if (pendingNotedOpsCopy.valueAt(i) == 0) {
+ pendingNotedOpsCopy.removeAt(i);
+ }
+ }
+ if (!pendingNotedOpsCopy.isEmpty()) {
+ try {
+ mService.noteOperationsInBatch(pendingNotedOpsCopy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }, NOTE_OP_BATCHING_DELAY_MILLIS);
+
+ sIsBatchedNoteOpCallScheduled = true;
+ }
+ return isDuplicated;
+ }
+ }
+
private int noteOpNoThrow(int op, int uid, @Nullable String packageName,
@Nullable String attributionTag, int virtualDeviceId, @Nullable String message) {
try {
@@ -9315,15 +9526,34 @@ public class AppOpsManager {
}
}
- SyncNotedAppOp syncOp;
- if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
- syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
- } else {
- syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
- virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
+ SyncNotedAppOp syncOp = null;
+ boolean isNoteOpDuplicated = false;
+ if (isNoteOpBatchingSupported()) {
+ int mode = sAppOpModeCache.query(
+ new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag,
+ "noteOpNoThrow"));
+ // For FOREGROUND mode, we still need to make a binder call to the system service
+ // to translate it to ALLOWED or IGNORED. So no batching is needed.
+ if (mode != MODE_FOREGROUND) {
+ isNoteOpDuplicated = batchDuplicateNoteOps(op, uid, packageName, attributionTag,
+ virtualDeviceId, message,
+ collectionMode == COLLECT_ASYNC, shouldCollectMessage);
+
+ syncOp = new SyncNotedAppOp(mode, op, attributionTag, packageName);
+ }
+ }
+
+ if (!isNoteOpDuplicated) {
+ if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+ syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
+ collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+ } else {
+ syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
+ virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage);
+ }
}
+
if (syncOp.getOpMode() == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
collectNotedOpForSelf(syncOp);
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b21defbcc0e3..8b7ea0f8b46a 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -29,7 +29,7 @@ import com.android.internal.app.IAppOpsCallback;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.UndecFunction;
@@ -86,9 +86,9 @@ public abstract class AppOpsManagerInternal {
*/
SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl);
+ @Nullable String message, boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+ Boolean, Integer, SyncNotedAppOp> superImpl);
/**
* Allows overriding note proxy operation behavior.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index da338474a448..2dead565fa85 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1751,6 +1751,19 @@ public class ApplicationPackageManager extends PackageManager {
}
}
+ /** @hide **/
+ @Override
+ public ProviderInfo resolveContentProviderForUid(@NonNull String authority,
+ ComponentInfoFlags flags, int callingUid) {
+ try {
+ return mPM.resolveContentProviderForUid(authority,
+ updateFlagsForComponent(flags.getValue(), getUserId(), null), getUserId(),
+ callingUid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
return queryContentProviders(processName, uid, ComponentInfoFlags.of(flags));
diff --git a/core/java/android/app/BackgroundStartPrivileges.java b/core/java/android/app/BackgroundStartPrivileges.java
index 20278eaee3b2..adea0a8a0702 100644
--- a/core/java/android/app/BackgroundStartPrivileges.java
+++ b/core/java/android/app/BackgroundStartPrivileges.java
@@ -23,12 +23,13 @@ import android.os.IBinder;
import com.android.internal.util.Preconditions;
import java.util.List;
+import java.util.Objects;
/**
* Privileges granted to a Process that allows it to execute starts from the background.
* @hide
*/
-public class BackgroundStartPrivileges {
+public final class BackgroundStartPrivileges {
/** No privileges. */
public static final BackgroundStartPrivileges NONE = new BackgroundStartPrivileges(
false, false, null);
@@ -190,4 +191,22 @@ public class BackgroundStartPrivileges {
+ ", originatingToken=" + mOriginatingToken
+ ']';
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BackgroundStartPrivileges that = (BackgroundStartPrivileges) o;
+ return mAllowsBackgroundActivityStarts == that.mAllowsBackgroundActivityStarts
+ && mAllowsBackgroundForegroundServiceStarts
+ == that.mAllowsBackgroundForegroundServiceStarts
+ && Objects.equals(mOriginatingToken, that.mOriginatingToken);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAllowsBackgroundActivityStarts,
+ mAllowsBackgroundForegroundServiceStarts,
+ mOriginatingToken);
+ }
}
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
index 1ff7a17e578f..d71ee7c712e7 100644
--- a/core/java/android/app/IUserSwitchObserver.aidl
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -19,10 +19,10 @@ package android.app;
import android.os.IRemoteCallback;
/** {@hide} */
-interface IUserSwitchObserver {
- void onBeforeUserSwitching(int newUserId);
- oneway void onUserSwitching(int newUserId, IRemoteCallback reply);
- oneway void onUserSwitchComplete(int newUserId);
- oneway void onForegroundProfileSwitch(int newProfileId);
- oneway void onLockedBootComplete(int newUserId);
+oneway interface IUserSwitchObserver {
+ void onBeforeUserSwitching(int newUserId, IRemoteCallback reply);
+ void onUserSwitching(int newUserId, IRemoteCallback reply);
+ void onUserSwitchComplete(int newUserId);
+ void onForegroundProfileSwitch(int newProfileId);
+ void onLockedBootComplete(int newUserId);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 17638ee76dba..eeb1ebb69b03 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11208,8 +11208,8 @@ public class Notification implements Parcelable
private static final String KEY_SEGMENT_LENGTH = "length";
private static final String KEY_POINT_POSITION = "position";
- private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
- private static final int MAX_PROGRESS_STOP_LIMIT = 5;
+ private static final int MAX_PROGRESS_SEGMENT_LIMIT = 10;
+ private static final int MAX_PROGRESS_POINT_LIMIT = 4;
private static final int DEFAULT_PROGRESS_MAX = 100;
private List<Segment> mProgressSegments = new ArrayList<>();
@@ -11286,7 +11286,9 @@ public class Notification implements Parcelable
mProgressSegments = new ArrayList<>();
}
mProgressSegments.clear();
- mProgressSegments.addAll(progressSegments);
+ for (Segment segment : progressSegments) {
+ addProgressSegment(segment);
+ }
return this;
}
@@ -11302,7 +11304,11 @@ public class Notification implements Parcelable
if (mProgressSegments == null) {
mProgressSegments = new ArrayList<>();
}
- mProgressSegments.add(segment);
+ if (segment.getLength() > 0) {
+ mProgressSegments.add(segment);
+ } else {
+ Log.w(TAG, "Dropped the segment. The length is not a positive integer.");
+ }
return this;
}
@@ -11327,7 +11333,14 @@ public class Notification implements Parcelable
* @see Point
*/
public @NonNull ProgressStyle setProgressPoints(@NonNull List<Point> points) {
- mProgressPoints = new ArrayList<>(points);
+ if (mProgressPoints == null) {
+ mProgressPoints = new ArrayList<>();
+ }
+ mProgressPoints.clear();
+
+ for (Point point: points) {
+ addProgressPoint(point);
+ }
return this;
}
@@ -11348,7 +11361,17 @@ public class Notification implements Parcelable
if (mProgressPoints == null) {
mProgressPoints = new ArrayList<>();
}
- mProgressPoints.add(point);
+ if (point.getPosition() >= 0) {
+ mProgressPoints.add(point);
+
+ if (mProgressPoints.size() > MAX_PROGRESS_POINT_LIMIT) {
+ Log.w(TAG, "Progress points limit is reached. First"
+ + MAX_PROGRESS_POINT_LIMIT + " points will be rendered.");
+ }
+
+ } else {
+ Log.w(TAG, "Dropped the point. The position is a negative integer.");
+ }
return this;
}
@@ -11384,8 +11407,7 @@ public class Notification implements Parcelable
} else {
int progressMax = 0;
int validSegmentCount = 0;
- for (int i = 0; i < progressSegment.size()
- && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) {
+ for (int i = 0; i < progressSegment.size(); i++) {
int segmentLength = progressSegment.get(i).getLength();
if (segmentLength > 0) {
try {
@@ -11832,6 +11854,30 @@ public class Notification implements Parcelable
totalLength = DEFAULT_PROGRESS_MAX;
segments.add(sanitizeSegment(new Segment(totalLength), backgroundColor,
defaultProgressColor));
+ } else if (segments.size() > MAX_PROGRESS_SEGMENT_LIMIT) {
+ // If segment limit is exceeded. All segments will be replaced
+ // with a single segment
+ boolean allSameColor = true;
+ int firstSegmentColor = segments.get(0).getColor();
+
+ for (int i = 1; i < segments.size(); i++) {
+ if (segments.get(i).getColor() != firstSegmentColor) {
+ allSameColor = false;
+ break;
+ }
+ }
+
+ // This single segment length has same max as total.
+ final Segment singleSegment = new Segment(totalLength);
+ // Single segment color: if all segments have the same color,
+ // use that color. Otherwise, use 0 / default.
+ singleSegment.setColor(allSameColor ? firstSegmentColor
+ : Notification.COLOR_DEFAULT);
+
+ segments.clear();
+ segments.add(sanitizeSegment(singleSegment,
+ backgroundColor,
+ defaultProgressColor));
}
// Ensure point color contrasts.
@@ -11840,6 +11886,9 @@ public class Notification implements Parcelable
final int position = point.getPosition();
if (position < 0 || position > totalLength) continue;
points.add(sanitizePoint(point, backgroundColor, defaultProgressColor));
+ if (points.size() == MAX_PROGRESS_POINT_LIMIT) {
+ break;
+ }
}
model = new NotificationProgressModel(segments, points,
@@ -11868,8 +11917,10 @@ public class Notification implements Parcelable
* has the same hue as the original color, but is lightened or darkened depending on
* whether the background is dark or light.
*
+ * @hide
*/
- private int sanitizeProgressColor(@ColorInt int color,
+ @VisibleForTesting
+ public static int sanitizeProgressColor(@ColorInt int color,
@ColorInt int bg,
@ColorInt int defaultColor) {
return Builder.ensureColorContrast(
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8ed66eb7e6c0..e9b889a2f1aa 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -55,6 +55,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -677,9 +678,14 @@ public class NotificationManager {
}
/** {@hide} */
- @UnsupportedAppUsage
- public NotificationManager(Context context, InstantSource clock)
+ public NotificationManager(Context context)
{
+ this(context, SystemClock.elapsedRealtimeClock());
+ }
+
+ /** {@hide} */
+ @UnsupportedAppUsage
+ public NotificationManager(Context context, InstantSource clock) {
mContext = context;
mClock = clock;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 920b19cd8f78..0bbe9434293a 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -17,7 +17,7 @@
package android.app;
import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
-import static android.provider.flags.Flags.stageFlagsForBuild;
+import static android.provider.flags.Flags.newStoragePublicApi;
import static android.server.Flags.removeGameManagerServiceFromWear;
import android.accounts.AccountManager;
@@ -289,7 +289,6 @@ import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import com.android.internal.util.Preconditions;
-import java.time.InstantSource;
import java.util.Map;
import java.util.Objects;
@@ -625,8 +624,8 @@ public final class SystemServiceRegistry {
com.android.internal.R.style.Theme_Dialog,
com.android.internal.R.style.Theme_Holo_Dialog,
com.android.internal.R.style.Theme_DeviceDefault_Dialog,
- com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
- InstantSource.system());
+ com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog))
+ );
}});
registerService(Context.PEOPLE_SERVICE, PeopleManager.class,
@@ -1841,7 +1840,7 @@ public final class SystemServiceRegistry {
VirtualizationFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
- if (stageFlagsForBuild()) {
+ if (newStoragePublicApi()) {
ConfigInfrastructureFrameworkInitializer.registerServiceWrappers();
}
diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java
index 727799a1f948..1664cfb6f7a8 100644
--- a/core/java/android/app/UserSwitchObserver.java
+++ b/core/java/android/app/UserSwitchObserver.java
@@ -30,7 +30,11 @@ public class UserSwitchObserver extends IUserSwitchObserver.Stub {
}
@Override
- public void onBeforeUserSwitching(int newUserId) throws RemoteException {}
+ public void onBeforeUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException {
+ if (reply != null) {
+ reply.sendResult(null);
+ }
+ }
@Override
public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException {
diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java
index 3438cc861661..ad43f271a910 100644
--- a/core/java/android/app/contextualsearch/ContextualSearchManager.java
+++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java
@@ -48,7 +48,9 @@ public final class ContextualSearchManager {
/**
* Key to get the entrypoint from the extras of the activity launched by contextual search.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_ENTRYPOINT =
"android.app.contextualsearch.extra.ENTRYPOINT";
@@ -56,14 +58,18 @@ public final class ContextualSearchManager {
/**
* Key to get the flag_secure value from the extras of the activity launched by contextual
* search. The value will be true if flag_secure is found in any of the visible activities.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_FLAG_SECURE_FOUND =
"android.app.contextualsearch.extra.FLAG_SECURE_FOUND";
/**
* Key to get the screenshot from the extras of the activity launched by contextual search.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_SCREENSHOT =
"android.app.contextualsearch.extra.SCREENSHOT";
@@ -71,7 +77,9 @@ public final class ContextualSearchManager {
/**
* Key to check whether managed profile is visible from the extras of the activity launched by
* contextual search. The value will be true if any one of the visible apps is managed.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE =
"android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE";
@@ -79,7 +87,9 @@ public final class ContextualSearchManager {
/**
* Key to get the list of visible packages from the extras of the activity launched by
* contextual search.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_VISIBLE_PACKAGE_NAMES =
"android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES";
@@ -87,7 +97,9 @@ public final class ContextualSearchManager {
/**
* Key to get the time the user made the invocation request, based on
* {@link SystemClock#uptimeMillis()}.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*
* TODO: un-hide in W
*
@@ -99,11 +111,24 @@ public final class ContextualSearchManager {
/**
* Key to get the binder token from the extras of the activity launched by contextual search.
* This token is needed to invoke {@link CallbackToken#getContextualSearchState} method.
- * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
*/
public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN";
/**
+ * Key to check whether audio is playing when contextual search is invoked.
+ * Only supposed to be used with ACTION_LAUNCH_CONTEXTUAL_SEARCH.
+ *
+ * @see #ACTION_LAUNCH_CONTEXTUAL_SEARCH
+ *
+ * @hide
+ */
+ public static final String EXTRA_IS_AUDIO_PLAYING =
+ "android.app.contextualsearch.extra.IS_AUDIO_PLAYING";
+
+ /**
* Intent action for contextual search invocation. The app providing the contextual search
* experience must add this intent filter action to the activity it wants to be launched.
* <br>
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index e8cfd79c9cc7..c19921dcdc61 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -8,6 +8,7 @@ flag {
bug: "309689654"
is_exported: true
}
+
flag {
name: "enable_token_refresh"
namespace: "machine_learning"
@@ -27,4 +28,11 @@ flag {
namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
bug: "372510690"
+}
+
+flag {
+ name: "include_audio_playing_status"
+ namespace: "sysui_integrations"
+ description: "Add audio playing status to the contextual search invocation intent."
+ bug: "372935419"
} \ No newline at end of file
diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java
index eea1d2ba5b9e..6ef6a44ddfbb 100644
--- a/core/java/android/app/jank/AppJankStats.java
+++ b/core/java/android/app/jank/AppJankStats.java
@@ -41,7 +41,8 @@ public final class AppJankStats {
// The id that has been set for the widget.
private String mWidgetId;
- // A general category that the widget applies to.
+ // A general category the widget falls into based on the functions it performs or helps
+ // facilitate.
private String mWidgetCategory;
// The states that the UI elements can report
@@ -53,78 +54,78 @@ public final class AppJankStats {
// Total number of frames determined to be janky during the reported state.
private long mJankyFrames;
- // Histogram of frame duration overruns encoded in predetermined buckets.
- private FrameOverrunHistogram mFrameOverrunHistogram;
+ // Histogram of relative frame times encoded in predetermined buckets.
+ private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram;
/** Used to indicate no widget category has been set. */
- public static final String WIDGET_CATEGORY_UNSPECIFIED =
- "widget_category_unspecified";
+ public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified";
/** UI elements that facilitate scrolling. */
- public static final String SCROLL = "scroll";
+ public static final String WIDGET_CATEGORY_SCROLL = "scroll";
/** UI elements that facilitate playing animations. */
- public static final String ANIMATION = "animation";
+ public static final String WIDGET_CATEGORY_ANIMATION = "animation";
/** UI elements that facilitate media playback. */
- public static final String MEDIA = "media";
+ public static final String WIDGET_CATEGORY_MEDIA = "media";
/** UI elements that facilitate in-app navigation. */
- public static final String NAVIGATION = "navigation";
+ public static final String WIDGET_CATEGORY_NAVIGATION = "navigation";
/** UI elements that facilitate displaying, hiding or interacting with keyboard. */
- public static final String KEYBOARD = "keyboard";
-
- /** UI elements that facilitate predictive back gesture navigation. */
- public static final String PREDICTIVE_BACK = "predictive_back";
+ public static final String WIDGET_CATEGORY_KEYBOARD = "keyboard";
/** UI elements that don't fall in one or any of the other categories. */
- public static final String OTHER = "other";
+ public static final String WIDGET_CATEGORY_OTHER = "other";
/** Used to indicate no widget state has been set. */
- public static final String WIDGET_STATE_UNSPECIFIED = "widget_state_unspecified";
+ public static final String WIDGET_STATE_UNSPECIFIED = "unspecified";
/** Used to indicate the UI element currently has no state and is idle. */
- public static final String NONE = "none";
+ public static final String WIDGET_STATE_NONE = "none";
/** Used to indicate the UI element is currently scrolling. */
- public static final String SCROLLING = "scrolling";
+ public static final String WIDGET_STATE_SCROLLING = "scrolling";
/** Used to indicate the UI element is currently being flung. */
- public static final String FLINGING = "flinging";
+ public static final String WIDGET_STATE_FLINGING = "flinging";
/** Used to indicate the UI element is currently being swiped. */
- public static final String SWIPING = "swiping";
+ public static final String WIDGET_STATE_SWIPING = "swiping";
/** Used to indicate the UI element is currently being dragged. */
- public static final String DRAGGING = "dragging";
+ public static final String WIDGET_STATE_DRAGGING = "dragging";
/** Used to indicate the UI element is currently zooming. */
- public static final String ZOOMING = "zooming";
+ public static final String WIDGET_STATE_ZOOMING = "zooming";
/** Used to indicate the UI element is currently animating. */
- public static final String ANIMATING = "animating";
+ public static final String WIDGET_STATE_ANIMATING = "animating";
/** Used to indicate the UI element is currently playing media. */
- public static final String PLAYBACK = "playback";
+ public static final String WIDGET_STATE_PLAYBACK = "playback";
/** Used to indicate the UI element is currently being tapped on, for example on a keyboard. */
- public static final String TAPPING = "tapping";
+ public static final String WIDGET_STATE_TAPPING = "tapping";
+
+ /** Used to indicate predictive back navigation is currently being used */
+ public static final String WIDGET_STATE_PREDICTIVE_BACK = "predictive_back";
/**
+ * Provide an organized way to group widgets that have similar purposes or perform related
+ * functions.
* @hide
*/
- @StringDef(value = {
+ @StringDef(prefix = {"WIDGET_CATEGORY_"}, value = {
WIDGET_CATEGORY_UNSPECIFIED,
- SCROLL,
- ANIMATION,
- MEDIA,
- NAVIGATION,
- KEYBOARD,
- PREDICTIVE_BACK,
- OTHER
+ WIDGET_CATEGORY_SCROLL,
+ WIDGET_CATEGORY_ANIMATION,
+ WIDGET_CATEGORY_MEDIA,
+ WIDGET_CATEGORY_NAVIGATION,
+ WIDGET_CATEGORY_KEYBOARD,
+ WIDGET_CATEGORY_OTHER
})
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.SOURCE)
@@ -133,17 +134,18 @@ public final class AppJankStats {
/**
* @hide
*/
- @StringDef(value = {
+ @StringDef(prefix = {"WIDGET_STATE_"}, value = {
WIDGET_STATE_UNSPECIFIED,
- NONE,
- SCROLLING,
- FLINGING,
- SWIPING,
- DRAGGING,
- ZOOMING,
- ANIMATING,
- PLAYBACK,
- TAPPING,
+ WIDGET_STATE_NONE,
+ WIDGET_STATE_SCROLLING,
+ WIDGET_STATE_FLINGING,
+ WIDGET_STATE_SWIPING,
+ WIDGET_STATE_DRAGGING,
+ WIDGET_STATE_ZOOMING,
+ WIDGET_STATE_ANIMATING,
+ WIDGET_STATE_PLAYBACK,
+ WIDGET_STATE_TAPPING,
+ WIDGET_STATE_PREDICTIVE_BACK
})
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.SOURCE)
@@ -156,31 +158,33 @@ public final class AppJankStats {
*
* @param appUid the Uid of the App that is collecting jank stats.
* @param widgetId the widget id that frames will be associated to.
- * @param widgetCategory a general functionality category that the widget falls into. Must be
- * one of the following: SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD,
- * PREDICTIVE_BACK, OTHER or will be set to WIDGET_CATEGORY_UNSPECIFIED
- * if no value is passed.
- * @param widgetState the state the widget was in while frames were counted. Must be one of
- * the following: NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING,
- * ANIMATING, PLAYBACK, TAPPING or will be set to WIDGET_STATE_UNSPECIFIED
- * if no value is passed.
+ * @param widgetCategory a category used to organize widgets in a structured way that indicates
+ * they serve a similar purpose or perform related functions. Must be
+ * prefixed with WIDGET_CATEGORY_ and have a suffix of one of the
+ * following:SCROLL, ANIMATION, MEDIA, NAVIGATION, KEYBOARD, OTHER or
+ * will be set to UNSPECIFIED if no value is passed.
+ * @param widgetState the state the widget was in while frames were counted. Must be prefixed
+ * with WIDGET_STATE_ and have a suffix of one of the following:
+ * NONE, SCROLLING, FLINGING, SWIPING, DRAGGING, ZOOMING, ANIMATING,
+ * PLAYBACK, TAPPING, PREDICTIVE_BACK or will be set to
+ * WIDGET_STATE_UNSPECIFIED if no value is passed.
* @param totalFrames the total number of frames that were counted for this stat.
* @param jankyFrames the total number of janky frames that were counted for this stat.
- * @param frameOverrunHistogram the histogram with predefined buckets. See
- * {@link #getFrameOverrunHistogram()} for details.
+ * @param relativeFrameTimeHistogram the histogram with predefined buckets. See
+ * {@link #getRelativeFrameTimeHistogram()} for details.
*
*/
public AppJankStats(int appUid, @NonNull String widgetId,
@Nullable @WidgetCategory String widgetCategory,
@Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames,
- @NonNull FrameOverrunHistogram frameOverrunHistogram) {
+ @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) {
mUid = appUid;
mWidgetId = widgetId;
mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED;
mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED;
mTotalFrames = totalFrames;
mJankyFrames = jankyFrames;
- mFrameOverrunHistogram = frameOverrunHistogram;
+ mRelativeFrameTimeHistogram = relativeFrameTimeHistogram;
}
/**
@@ -203,7 +207,7 @@ public final class AppJankStats {
/**
* Returns the category that the widget's functionality generally falls into, or
- * widget_category_unspecified {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in.
+ * {@link #WIDGET_CATEGORY_UNSPECIFIED} if no value was passed in.
*
* @return the category that the widget's functionality generally falls into, this value cannot
* be null.
@@ -213,7 +217,7 @@ public final class AppJankStats {
}
/**
- * Returns the widget's state that was reported for this stat, or widget_state_unspecified
+ * Returns the widget's state that was reported for this stat, or
* {@link #WIDGET_STATE_UNSPECIFIED} if no value was passed in.
*
* @return the widget's state that was reported for this stat. This value cannot be null.
@@ -241,13 +245,13 @@ public final class AppJankStats {
}
/**
- * Returns a Histogram containing frame overrun times in millis grouped into predefined buckets.
- * See {@link FrameOverrunHistogram} for more information.
+ * Returns a Histogram containing relative frame times in millis grouped into predefined
+ * buckets. See {@link RelativeFrameTimeHistogram} for more information.
*
- * @return Histogram containing frame overrun times in predefined buckets. This value cannot
+ * @return Histogram containing relative frame times in predefined buckets. This value cannot
* be null.
*/
- public @NonNull FrameOverrunHistogram getFrameOverrunHistogram() {
- return mFrameOverrunHistogram;
+ public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() {
+ return mRelativeFrameTimeHistogram;
}
}
diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java
deleted file mode 100644
index 3ad6531a46bf..000000000000
--- a/core/java/android/app/jank/FrameOverrunHistogram.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.jank;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-
-import java.util.Arrays;
-
-/**
- * This class is intended to be used when reporting {@link AppJankStats} back to the system. It's
- * intended to be used by library widgets to help facilitate the reporting of frame overrun times
- * by adding those times into predefined buckets.
- */
-@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
-public class FrameOverrunHistogram {
- private static int[] sBucketEndpoints = new int[]{
- Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18,
- -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40,
- 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
- };
- private int[] mBucketCounts;
-
- /**
- * Create a new instance of FrameOverrunHistogram.
- */
- public FrameOverrunHistogram() {
- mBucketCounts = new int[sBucketEndpoints.length];
- }
-
- /**
- * Increases the count by one for the bucket representing the frame overrun duration.
- *
- * @param frameOverrunMillis frame overrun duration in millis, frame overrun is the difference
- * between a frames deadline and when it was rendered.
- */
- public void addFrameOverrunMillis(int frameOverrunMillis) {
- int countsIndex = getIndexForCountsFromOverrunTime(frameOverrunMillis);
- mBucketCounts[countsIndex]++;
- }
-
- /**
- * Returns the counts for the all the frame overrun buckets.
- *
- * @return an array of integers representing the counts of frame overrun times. This value
- * cannot be null.
- */
- public @NonNull int[] getBucketCounters() {
- return Arrays.copyOf(mBucketCounts, mBucketCounts.length);
- }
-
- /**
- * Returns the predefined endpoints for the histogram.
- *
- * @return array of integers representing the endpoints for the predefined histogram count
- * buckets. This value cannot be null.
- */
- public @NonNull int[] getBucketEndpointsMillis() {
- return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length);
- }
-
- // This takes the overrun time and returns what bucket it belongs to in the counters array.
- private int getIndexForCountsFromOverrunTime(int overrunTime) {
- if (overrunTime < 20) {
- if (overrunTime >= -20) {
- return (overrunTime + 20) / 2 + 12;
- }
- if (overrunTime >= -30) {
- return (overrunTime + 30) / 5 + 10;
- }
- if (overrunTime >= -100) {
- return (overrunTime + 100) / 10 + 3;
- }
- if (overrunTime >= -200) {
- return (overrunTime + 200) / 50 + 1;
- }
- return 0;
- }
- if (overrunTime < 30) {
- return (overrunTime - 20) / 5 + 32;
- }
- if (overrunTime < 100) {
- return (overrunTime - 30) / 10 + 34;
- }
- if (overrunTime < 200) {
- return (overrunTime - 50) / 100 + 41;
- }
- if (overrunTime < 1000) {
- return (overrunTime - 200) / 100 + 43;
- }
- return sBucketEndpoints.length - 1;
- }
-}
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index c9472598b352..b4c293eeb695 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -111,7 +111,7 @@ public class JankDataProcessor {
pendingStat.mTotalFrames += jankStat.getTotalFrameCount();
mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
- jankStat.getFrameOverrunHistogram().getBucketCounters());
+ jankStat.getRelativeFrameTimeHistogram().getBucketCounters());
}
private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) {
@@ -136,7 +136,7 @@ public class JankDataProcessor {
pendingStat.mJankyFrames = jankStats.getJankyFrameCount();
mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
- jankStats.getFrameOverrunHistogram().getBucketCounters());
+ jankStats.getRelativeFrameTimeHistogram().getBucketCounters());
mPendingJankStats.put(stateKey, pendingStat);
}
@@ -271,7 +271,8 @@ public class JankDataProcessor {
private static final int[] sFrameOverrunHistogramBounds = {
Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20,
-18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25,
- 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
+ 30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
+ Integer.MAX_VALUE
};
private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
@@ -414,7 +415,7 @@ public class JankDataProcessor {
if (overrunTime < 200) {
return (overrunTime - 50) / 100 + 41;
}
- if (overrunTime < 1000) {
+ if (overrunTime <= 1000) {
return (overrunTime - 200) / 100 + 43;
}
return sFrameOverrunHistogramBounds.length - 1;
diff --git a/core/java/android/app/jank/RelativeFrameTimeHistogram.java b/core/java/android/app/jank/RelativeFrameTimeHistogram.java
new file mode 100644
index 000000000000..666f90f89f45
--- /dev/null
+++ b/core/java/android/app/jank/RelativeFrameTimeHistogram.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * A histogram of frame times relative to their deadline.
+ *
+ * This class aids in reporting {@link AppJankStats} to the system and is designed for use by
+ * library widgets. It facilitates the recording of frame times in relation to the frame deadline.
+ * The class records the distribution of time remaining until a frame is considered janky or how
+ * janky the frame was.
+ * <p>
+ * A frame's relative frame time value indicates whether it was delivered early, on time, or late.
+ * A negative relative frame time value indicates the frame was delivered early, a value of zero
+ * indicates the frame was delivered on time and a positive value indicates the frame was delivered
+ * late. The values of the endpoints indicate how early or late a frame was delivered.
+ * <p>
+ * The relative frame times are recorded as a histogram: values are
+ * {@link #addRelativeFrameTimeMillis added} to a bucket by increasing the bucket's counter. The
+ * count of frames with a relative frame time between
+ * {@link #getBucketEndpointsMillis bucket endpoints} {@code i} and {@code i+1} can be obtained
+ * through index {@code i} of {@link #getBucketCounters}.
+ *
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class RelativeFrameTimeHistogram {
+ private static int[] sBucketEndpoints = new int[]{
+ Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20, -18,
+ -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25, 30, 40,
+ 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
+ Integer.MAX_VALUE
+ };
+ //
+ private int[] mBucketCounts;
+
+ /**
+ * Create a new instance of RelativeFrameTimeHistogram.
+ */
+ public RelativeFrameTimeHistogram() {
+ mBucketCounts = new int[sBucketEndpoints.length - 1];
+ }
+
+ /**
+ * Increases the count by one for the bucket representing the relative frame time.
+ *
+ * @param frameTimeMillis relative frame time in millis, relative frame time is the difference
+ * between a frames deadline and when it was rendered.
+ */
+ public void addRelativeFrameTimeMillis(int frameTimeMillis) {
+ int countsIndex = getRelativeFrameTimeBucketIndex(frameTimeMillis);
+ mBucketCounts[countsIndex]++;
+ }
+
+ /**
+ * Returns the counts for the all the relative frame time buckets.
+ *
+ * @return an array of integers representing the counts of relative frame times. This value
+ * cannot be null.
+ */
+ public @NonNull int[] getBucketCounters() {
+ return Arrays.copyOf(mBucketCounts, mBucketCounts.length);
+ }
+
+ /**
+ * Returns the relative frame time endpoints for the histogram.
+ * <p>
+ * Index {@code i} of {@link #getBucketCounters} contains the count of frames that had a
+ * relative frame time between {@code endpoints[i]} (inclusive) and {@code endpoints[i+1]}
+ * (exclusive).
+ *
+ * @return array of integers representing the endpoints for the predefined histogram count
+ * buckets. This value cannot be null.
+ */
+ public @NonNull int[] getBucketEndpointsMillis() {
+ return Arrays.copyOf(sBucketEndpoints, sBucketEndpoints.length);
+ }
+
+ // This takes the relative frame time and returns what bucket it belongs to in the counters
+ // array.
+ private int getRelativeFrameTimeBucketIndex(int relativeFrameTime) {
+ if (relativeFrameTime < 20) {
+ if (relativeFrameTime >= -20) {
+ return (relativeFrameTime + 20) / 2 + 12;
+ }
+ if (relativeFrameTime >= -30) {
+ return (relativeFrameTime + 30) / 5 + 10;
+ }
+ if (relativeFrameTime >= -100) {
+ return (relativeFrameTime + 100) / 10 + 3;
+ }
+ if (relativeFrameTime >= -200) {
+ return (relativeFrameTime + 200) / 50 + 1;
+ }
+ return 0;
+ }
+ if (relativeFrameTime < 30) {
+ return (relativeFrameTime - 20) / 5 + 32;
+ }
+ if (relativeFrameTime < 100) {
+ return (relativeFrameTime - 30) / 10 + 34;
+ }
+ if (relativeFrameTime < 200) {
+ return (relativeFrameTime - 50) / 100 + 41;
+ }
+ if (relativeFrameTime < 1000) {
+ return (relativeFrameTime - 200) / 100 + 43;
+ }
+ return mBucketCounts.length - 1;
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9f898b823a76..e6ddbf466cae 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -196,6 +196,21 @@ interface IPackageManager {
ProviderInfo resolveContentProvider(String name, long flags, int userId);
/**
+ * Resolve content providers with a given authority, for a specific
+ * callingUid.
+ *
+ * @param authority Authority of the content provider
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId Current user ID
+ * @param callingUid UID of the caller who's access to the content provider
+ is to be checked
+ *
+ * @return ProviderInfo of the resolved content provider. May return null
+ */
+ ProviderInfo resolveContentProviderForUid(String authority, long flags,
+ int userId, int callingUid);
+
+ /**
* Retrieve sync information for all content providers.
*
* @param outNames Filled in with a list of the root names of the content
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 438a21b7942f..c16582f19c9b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -8349,6 +8349,25 @@ public abstract class PackageManager {
}
/**
+ * Resolve content providers with a given authority, for a specific callingUid.
+ * @param authority Authority of the content provider
+ * @param flags Additional option flags to modify the data returned.
+ * @param callingUid UID of the caller who's access to the content provider is to be checked
+
+ * @return ProviderInfo of the resolved content provider.
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(android.content.pm.Flags.FLAG_UID_BASED_PROVIDER_LOOKUP)
+ @RequiresPermission(Manifest.permission.RESOLVE_COMPONENT_FOR_UID)
+ @SystemApi
+ public ProviderInfo resolveContentProviderForUid(@NonNull String authority,
+ @NonNull ComponentInfoFlags flags, int callingUid) {
+ throw new UnsupportedOperationException(
+ "resolveContentProviderForUid not implemented in subclass");
+ }
+
+ /**
* Retrieve content provider information.
* <p>
* <em>Note: unlike most other methods, an empty result set is indicated
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 0d219a901b9d..7bba06c87813 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -152,7 +152,7 @@ flag {
name: "cache_sdk_system_features"
namespace: "system_performance"
description: "Feature flag to enable optimized cache for SDK-defined system feature lookups."
- bug: "375000483"
+ bug: "326623529"
}
flag {
@@ -375,3 +375,11 @@ flag {
description: "Feature flag to remove the consumption of the hidden module status (ModuleInfo#IsHidden) in the Android source tree."
bug: "363952383"
}
+
+flag {
+ name: "uid_based_provider_lookup"
+ is_exported: true
+ namespace: "package_manager_service"
+ bug: "334024639"
+ description: "Feature flag to check whether a given UID can access a content provider"
+}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 852f04793f15..9c6b71b72ec8 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -417,6 +417,7 @@ public abstract class CameraDevice implements AutoCloseable {
* or if any of the output configurations sets a stream use
* case different from {@link
* android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT}.
+ * @throws UnsupportedOperationException if the camera has been opened in shared mode
* @see CameraExtensionCharacteristics#getSupportedExtensions
* @see CameraExtensionCharacteristics#getExtensionSupportedSizes
*/
@@ -1258,7 +1259,8 @@ public abstract class CameraDevice implements AutoCloseable {
* configurations are empty; or the session configuration
* executor is invalid;
* or the output dynamic range combination is
- * invalid/unsupported.
+ * invalid/unsupported; or the session type is not shared when
+ * camera has been opened in shared mode.
* @throws CameraAccessException In case the camera device is no longer connected or has
* encountered a fatal error.
* @see #createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)
@@ -1292,6 +1294,8 @@ public abstract class CameraDevice implements AutoCloseable {
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
+ * @throws UnsupportedOperationException if this is not a primary client of a camera opened in
+ * shared mode
*/
@NonNull
public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
@@ -1328,6 +1332,8 @@ public abstract class CameraDevice implements AutoCloseable {
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
+ * @throws UnsupportedOperationException if this is not a primary client of a camera opened in
+ * shared mode
*
* @see #TEMPLATE_PREVIEW
* @see #TEMPLATE_RECORD
@@ -1369,6 +1375,7 @@ public abstract class CameraDevice implements AutoCloseable {
* @throws CameraAccessException if the camera device is no longer connected or has
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
+ * @throws UnsupportedOperationException if the camera has been opened in shared mode
*
* @see CaptureRequest.Builder
* @see TotalCaptureResult
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index aba2345f28d8..bfaff941939c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1375,6 +1375,9 @@ public final class CameraManager {
* @throws SecurityException if the application does not have permission to
* access the camera
*
+ * @throws UnsupportedOperationException if {@link #isCameraDeviceSharingSupported} returns
+ * false for the given {@code cameraId}.
+ *
* @see #getCameraIdList
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*
@@ -1393,6 +1396,10 @@ public final class CameraManager {
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
+ if (!isCameraDeviceSharingSupported(cameraId)) {
+ throw new UnsupportedOperationException(
+ "CameraDevice sharing is not supported for Camera ID: " + cameraId);
+ }
openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
getRotationOverride(mContext), /*sharedMode*/true);
}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 496d316eb028..1c65b0882e0f 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -299,6 +299,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
return mRequestType;
}
+ /**
+ * Get the stream ids corresponding to the target surfaces.
+ *
+ * @hide
+ */
+ public int[] getStreamIds() {
+ return mStreamIdxArray;
+ };
+
+ /**
+ * Get the surface ids corresponding to the target surfaces.
+ *
+ * @hide
+ */
+ public int[] getSurfaceIds() {
+ return mSurfaceIdxArray;
+ };
+
// If this request is part of constrained high speed request list that was created by
// {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList}
private boolean mIsPartOfCHSRequestList = false;
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index ce8661e90978..7e0456b22be8 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -340,6 +340,30 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession
}
}
+ /**
+ * Shared Camera capture session API which can be used by the clients
+ * to start streaming.
+ *
+ * @hide
+ */
+ public int startStreaming(List<Surface> surfaces, Executor executor,
+ CaptureCallback callback) throws CameraAccessException {
+
+ synchronized (mDeviceImpl.mInterfaceLock) {
+ checkNotClosed();
+
+ executor = CameraDeviceImpl.checkExecutor(executor, callback);
+
+ if (DEBUG) {
+ Log.v(TAG, mIdString + "startStreaming callback " + callback + " executor"
+ + " " + executor);
+ }
+
+ return addPendingSequence(mDeviceImpl.startStreaming(surfaces,
+ createCaptureCallbackProxyWithExecutor(executor, callback), mDeviceExecutor));
+ }
+ }
+
private void checkRepeatingRequest(CaptureRequest request) {
if (request == null) {
throw new IllegalArgumentException("request must not be null");
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 34c0f7b19da9..89a6b02b56c4 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -451,6 +451,16 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ /**
+ * When camera device is opened in shared mode, call to check if this is a primary client.
+ *
+ */
+ public boolean isPrimaryClient() {
+ synchronized (mInterfaceLock) {
+ return mIsPrimaryClient;
+ }
+ }
+
private Map<String, CameraCharacteristics> getPhysicalIdToChars() {
if (mPhysicalIdsToChars == null) {
try {
@@ -482,8 +492,19 @@ public class CameraDeviceImpl extends CameraDevice
mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
Parcel resultParcel = Parcel.obtain();
- mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel, 0);
+
+ // Passing in PARCELABLE_WRITE_RETURN_VALUE closes the ParcelFileDescriptors
+ // owned by MQDescriptor returned by getCaptureResultMetadataQueue()
+ // Though these will be closed when GC runs, that may not happen for a while.
+ // Also, apps running with StrictMode would get warnings / crash in the case they're not
+ // explicitly closed.
+ mRemoteDevice.getCaptureResultMetadataQueue().writeToParcel(resultParcel,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
mFMQReader = nativeCreateFMQReader(resultParcel);
+ // Recycle since resultParcel would dup fds from MQDescriptor as well. We don't
+ // need them after the native FMQ reader has been created. That is since the native
+ // creates calls MQDescriptor.readFromParcel() which again dups the fds.
+ resultParcel.recycle();
IBinder remoteDeviceBinder = remoteDevice.asBinder();
// For legacy camera device, remoteDevice is in the same process, and
@@ -847,24 +868,19 @@ public class CameraDeviceImpl extends CameraDevice
List<SharedOutputConfiguration> sharedConfigs =
sharedSessionConfiguration.getOutputStreamsInformation();
for (SharedOutputConfiguration sharedConfig : sharedConfigs) {
- if (outConfig.getConfiguredSize().equals(sharedConfig.getSize())
- && (outConfig.getConfiguredFormat() == sharedConfig.getFormat())
- && (outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE)
- && (outConfig.getSurfaceType() == sharedConfig.getSurfaceType())
+ if ((outConfig.getSurfaceGroupId() == OutputConfiguration.SURFACE_GROUP_ID_NONE)
&& (outConfig.getMirrorMode() == sharedConfig.getMirrorMode())
- && (outConfig.getUsage() == sharedConfig.getUsage())
&& (outConfig.isReadoutTimestampEnabled()
== sharedConfig.isReadoutTimestampEnabled())
&& (outConfig.getTimestampBase() == sharedConfig.getTimestampBase())
&& (outConfig.getStreamUseCase() == sharedConfig.getStreamUseCase())
- && (outConfig.getColorSpace().equals(
- sharedSessionConfiguration.getColorSpace()))
&& (outConfig.getDynamicRangeProfile()
== DynamicRangeProfiles.STANDARD)
- && (outConfig.getConfiguredDataspace() == sharedConfig.getDataspace())
&& (Objects.equals(outConfig.getPhysicalCameraId(),
sharedConfig.getPhysicalCameraId()))
&& (outConfig.getSensorPixelModes().isEmpty())
+ && (!outConfig.isMultiResolution())
+ && (!outConfig.isDeferredConfiguration())
&& (!outConfig.isShared())) {
//Found valid config, return true
return true;
@@ -896,14 +912,6 @@ public class CameraDeviceImpl extends CameraDevice
if (config.getExecutor() == null) {
throw new IllegalArgumentException("Invalid executor");
}
- if (mSharedMode) {
- if (config.getSessionType() != SessionConfiguration.SESSION_SHARED) {
- throw new IllegalArgumentException("Invalid session type");
- }
- if (!checkSharedSessionConfiguration(outputConfigs)) {
- throw new IllegalArgumentException("Invalid output configurations");
- }
- }
createCaptureSessionInternal(config.getInputConfiguration(), outputConfigs,
config.getStateCallback(), config.getExecutor(), config.getSessionType(),
config.getSessionParameters());
@@ -921,17 +929,26 @@ public class CameraDeviceImpl extends CameraDevice
checkIfCameraClosedOrInError();
+ boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE);
+ if (Flags.cameraMultiClient() && mSharedMode) {
+ if (!isSharedSession) {
+ throw new IllegalArgumentException("Invalid session type");
+ }
+ if (!checkSharedSessionConfiguration(outputConfigurations)) {
+ throw new IllegalArgumentException("Invalid output configurations");
+ }
+ if (inputConfig != null) {
+ throw new IllegalArgumentException("Shared capture session doesn't support"
+ + " input configuration yet.");
+ }
+ }
+
boolean isConstrainedHighSpeed =
(operatingMode == ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE);
if (isConstrainedHighSpeed && inputConfig != null) {
throw new IllegalArgumentException("Constrained high speed session doesn't support"
+ " input configuration yet.");
}
- boolean isSharedSession = (operatingMode == ICameraDeviceUser.SHARED_MODE);
- if (isSharedSession && inputConfig != null) {
- throw new IllegalArgumentException("Shared capture session doesn't support"
- + " input configuration yet.");
- }
if (mCurrentExtensionSession != null) {
mCurrentExtensionSession.commitStats();
@@ -993,8 +1010,7 @@ public class CameraDeviceImpl extends CameraDevice
mCharacteristics);
} else if (isSharedSession) {
newSession = new CameraSharedCaptureSessionImpl(mNextSessionId++,
- callback, executor, this, mDeviceExecutor, configureSuccess,
- mIsPrimaryClient);
+ callback, executor, this, mDeviceExecutor, configureSuccess);
} else {
newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
callback, executor, this, mDeviceExecutor, configureSuccess);
@@ -1063,6 +1079,11 @@ public class CameraDeviceImpl extends CameraDevice
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
+ if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) {
+ throw new UnsupportedOperationException("In shared session mode,"
+ + "only primary clients can create capture request.");
+ }
+
for (String physicalId : physicalCameraIdSet) {
if (Objects.equals(physicalId, getId())) {
throw new IllegalStateException("Physical id matches the logical id!");
@@ -1089,6 +1110,11 @@ public class CameraDeviceImpl extends CameraDevice
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
+ if (Flags.cameraMultiClient() && mSharedMode && !mIsPrimaryClient) {
+ throw new UnsupportedOperationException("In shared session mode,"
+ + "only primary clients can create capture request.");
+ }
+
CameraMetadataNative templatedRequest = null;
templatedRequest = mRemoteDevice.createDefaultRequest(templateType);
@@ -1108,6 +1134,10 @@ public class CameraDeviceImpl extends CameraDevice
throws CameraAccessException {
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
+ if (Flags.cameraMultiClient() && mSharedMode) {
+ throw new UnsupportedOperationException("In shared session mode,"
+ + "reprocess capture requests are not supported.");
+ }
CameraMetadataNative resultMetadata = new
CameraMetadataNative(inputResult.getNativeCopy());
@@ -1561,6 +1591,74 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ public int startStreaming(List<Surface> surfaces, CaptureCallback callback,
+ Executor executor) throws CameraAccessException {
+ // Need a valid executor, or current thread needs to have a looper, if
+ // callback is valid
+ executor = checkExecutor(executor, callback);
+ synchronized (mInterfaceLock) {
+ checkIfCameraClosedOrInError();
+ for (Surface surface : surfaces) {
+ if (surface == null) {
+ throw new IllegalArgumentException("Null Surface targets are not allowed");
+ }
+ }
+ // In shared session mode, if there are other active clients streaming then
+ // stoprepeating does not actually send request to HAL to cancel the request.
+ // Cameraservice will use this call to remove this client surfaces provided in its
+ // previous streaming request. If this is the only client for the shared camera device
+ // then camerservice will ask HAL to cancel the previous repeating request
+ stopRepeating();
+
+ // StartStreaming API does not allow capture parameters to be provided through a capture
+ // request. If the primary client has an existing repeating request, the camera service
+ // will either attach the provided surfaces to that request or create a default capture
+ // request if no repeating request is active. A default capture request is created here
+ // for initial use. The capture callback will provide capture results that include the
+ // actual capture parameters used for the streaming.
+ CaptureRequest.Builder builder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ for (Surface surface : surfaces) {
+ builder.addTarget(surface);
+ }
+ CaptureRequest request = builder.build();
+ request.convertSurfaceToStreamId(mConfiguredOutputs);
+
+ SubmitInfo requestInfo;
+ requestInfo = mRemoteDevice.startStreaming(request.getStreamIds(),
+ request.getSurfaceIds());
+ request.recoverStreamIdToSurface();
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+
+ if (callback != null) {
+ mCaptureCallbackMap.put(requestInfo.getRequestId(),
+ new CaptureCallbackHolder(
+ callback, requestList, executor, true, mNextSessionId - 1));
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null");
+ }
+ }
+
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId,
+ requestInfo.getLastFrameNumber(), mRepeatingRequestTypes);
+ }
+
+ CaptureRequest[] requestArray = requestList.toArray(
+ new CaptureRequest[requestList.size()]);
+ mRepeatingRequestId = requestInfo.getRequestId();
+ mRepeatingRequestTypes = getRequestTypes(requestArray);
+
+ if (mIdle) {
+ mDeviceExecutor.execute(mCallOnActive);
+ }
+ mIdle = false;
+
+ return requestInfo.getRequestId();
+ }
+ }
+
public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
Executor executor) throws CameraAccessException {
List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
@@ -2883,6 +2981,11 @@ public class CameraDeviceImpl extends CameraDevice
@Override
public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration)
throws CameraAccessException {
+ if (Flags.cameraMultiClient() && mSharedMode) {
+ throw new UnsupportedOperationException("In shared session mode,"
+ + "extension sessions are not supported.");
+ }
+
HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>(
getPhysicalIdToChars());
characteristicsMap.put(mCameraId, mCharacteristics);
@@ -2918,4 +3021,4 @@ public class CameraDeviceImpl extends CameraDevice
}
}
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java
index a1f31c0ced5e..8c0dcfb2a28c 100644
--- a/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraSharedCaptureSessionImpl.java
@@ -19,6 +19,8 @@ import android.annotation.FlaggedApi;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraOfflineSession;
+import android.hardware.camera2.CameraOfflineSession.CameraOfflineSessionCallback;
import android.hardware.camera2.CameraSharedCaptureSession;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.OutputConfiguration;
@@ -28,6 +30,7 @@ import android.view.Surface;
import com.android.internal.camera.flags.Flags;
+import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
@@ -46,7 +49,8 @@ public class CameraSharedCaptureSessionImpl
private static final String TAG = "CameraSharedCaptureSessionImpl";
private final CameraCaptureSessionImpl mSessionImpl;
private final ConditionVariable mInitialized = new ConditionVariable();
- private boolean mIsPrimary;
+ private final android.hardware.camera2.impl.CameraDeviceImpl mCameraDevice;
+ private final Executor mDeviceExecutor;
/**
* Create a new CameraCaptureSession.
@@ -54,24 +58,32 @@ public class CameraSharedCaptureSessionImpl
CameraSharedCaptureSessionImpl(int id,
CameraCaptureSession.StateCallback callback, Executor stateExecutor,
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
- Executor deviceStateExecutor, boolean configureSuccess, boolean isPrimary) {
+ Executor deviceStateExecutor, boolean configureSuccess) {
CameraCaptureSession.StateCallback wrapperCallback = new WrapperCallback(callback);
mSessionImpl = new CameraCaptureSessionImpl(id, /*input*/null, wrapperCallback,
stateExecutor, deviceImpl, deviceStateExecutor, configureSuccess);
- mIsPrimary = isPrimary;
+ mCameraDevice = deviceImpl;
+ mDeviceExecutor = deviceStateExecutor;
mInitialized.open();
}
@Override
- public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback listener)
+ public int startStreaming(List<Surface> surfaces, Executor executor, CaptureCallback callback)
throws CameraAccessException {
- // Todo: Need to add implementation.
- return 0;
+ if (surfaces.isEmpty()) {
+ throw new IllegalArgumentException("No surfaces provided for streaming");
+ } else if (executor == null) {
+ throw new IllegalArgumentException("executor must not be null");
+ } else if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ return mSessionImpl.startStreaming(surfaces, executor, callback);
}
@Override
public void stopStreaming() throws CameraAccessException {
- // Todo: Need to add implementation.
+ mSessionImpl.stopRepeating();
}
@Override
@@ -90,16 +102,24 @@ public class CameraSharedCaptureSessionImpl
}
@Override
+ public boolean supportsOfflineProcessing(Surface surface) {
+ return false;
+ }
+
+ @Override
public void abortCaptures() throws CameraAccessException {
- if (mIsPrimary) {
+ if (mCameraDevice.isPrimaryClient()) {
mSessionImpl.abortCaptures();
+ return;
}
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
}
@Override
public int setRepeatingRequest(CaptureRequest request, CaptureCallback listener,
Handler handler) throws CameraAccessException {
- if (mIsPrimary) {
+ if (mCameraDevice.isPrimaryClient()) {
return mSessionImpl.setRepeatingRequest(request, listener, handler);
}
throw new UnsupportedOperationException("Shared capture session only supports this method"
@@ -107,16 +127,30 @@ public class CameraSharedCaptureSessionImpl
}
@Override
+ public int setSingleRepeatingRequest(CaptureRequest request, Executor executor,
+ CaptureCallback listener)
+ throws CameraAccessException {
+ if (mCameraDevice.isPrimaryClient()) {
+ return mSessionImpl.setSingleRepeatingRequest(request, executor, listener);
+ }
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
+ }
+
+ @Override
public void stopRepeating() throws CameraAccessException {
- if (mIsPrimary) {
+ if (mCameraDevice.isPrimaryClient()) {
mSessionImpl.stopRepeating();
+ return;
}
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
}
@Override
public int capture(CaptureRequest request, CaptureCallback listener, Handler handler)
throws CameraAccessException {
- if (mIsPrimary) {
+ if (mCameraDevice.isPrimaryClient()) {
return mSessionImpl.capture(request, listener, handler);
}
throw new UnsupportedOperationException("Shared capture session only supports this method"
@@ -124,6 +158,17 @@ public class CameraSharedCaptureSessionImpl
}
@Override
+ public int captureSingleRequest(CaptureRequest request, Executor executor,
+ CaptureCallback listener)
+ throws CameraAccessException {
+ if (mCameraDevice.isPrimaryClient()) {
+ return mSessionImpl.captureSingleRequest(request, executor, listener);
+ }
+ throw new UnsupportedOperationException("Shared capture session only supports this method"
+ + " for primary clients");
+ }
+
+ @Override
public void tearDown(Surface surface) throws CameraAccessException {
mSessionImpl.tearDown(surface);
}
@@ -149,48 +194,72 @@ public class CameraSharedCaptureSessionImpl
}
@Override
+ public CameraOfflineSession switchToOffline(Collection<Surface> offlineSurfaces,
+ Executor executor, CameraOfflineSessionCallback listener)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared capture session do not support this method"
+ );
+ }
+
+ @Override
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback listener,
Handler handler) throws CameraAccessException {
- throw new UnsupportedOperationException("Shared Capture session doesn't support"
+ throw new UnsupportedOperationException("Shared Capture session do not support"
+ + " this method");
+ }
+
+ @Override
+ public int setRepeatingBurstRequests(List<CaptureRequest> requests,
+ Executor executor, CaptureCallback listener)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared Capture session do not support"
+ " this method");
}
@Override
public int captureBurst(List<CaptureRequest> requests, CaptureCallback listener,
Handler handler) throws CameraAccessException {
- throw new UnsupportedOperationException("Shared Capture session doesn't support"
+ throw new UnsupportedOperationException("Shared Capture session do not support"
+ + " this method");
+ }
+
+ @Override
+ public int captureBurstRequests(List<CaptureRequest> requests,
+ Executor executor, CaptureCallback listener)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Shared Capture session do not support"
+ " this method");
}
@Override
public void updateOutputConfiguration(OutputConfiguration config)
throws CameraAccessException {
- throw new UnsupportedOperationException("Shared capture session doesn't support"
+ throw new UnsupportedOperationException("Shared capture session do not support"
+ " this method");
}
@Override
public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs)
throws CameraAccessException {
- throw new UnsupportedOperationException("Shared capture session doesn't support"
+ throw new UnsupportedOperationException("Shared capture session do not support"
+ " this method");
}
@Override
public void prepare(Surface surface) throws CameraAccessException {
- throw new UnsupportedOperationException("Shared capture session doesn't support"
+ throw new UnsupportedOperationException("Shared capture session do not support"
+ " this method");
}
@Override
public void prepare(int maxCount, Surface surface) throws CameraAccessException {
- throw new UnsupportedOperationException("Shared capture session doesn't support"
+ throw new UnsupportedOperationException("Shared capture session do not support"
+ " this method");
}
@Override
public void closeWithoutDraining() {
- throw new UnsupportedOperationException("Shared capture session doesn't support"
+ throw new UnsupportedOperationException("Shared capture session do not support"
+ " this method");
}
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index a79e084b7f41..0b8e9c2687c3 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -65,6 +65,17 @@ public class ICameraDeviceUserWrapper {
}
}
+ public SubmitInfo startStreaming(int[] streamIdxArray, int[] surfaceIdxArray)
+ throws CameraAccessException {
+ try {
+ return mRemoteDevice.startStreaming(streamIdxArray, surfaceIdxArray);
+ } catch (ServiceSpecificException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw ExceptionUtils.throwAsPublicException(e);
+ }
+ }
+
public SubmitInfo submitRequest(CaptureRequest request, boolean streaming)
throws CameraAccessException {
try {
@@ -325,4 +336,4 @@ public class ICameraDeviceUserWrapper {
}
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index e12c46322d8c..d394154a2c0e 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1803,6 +1803,19 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Get the flag indicating if this {@link OutputConfiguration} is for a multi-resolution output
+ * with a MultiResolutionImageReader.
+ *
+ * @return true if this {@link OutputConfiguration} is for a multi-resolution output with a
+ * MultiResolutionImageReader.
+ *
+ * @hide
+ */
+ public boolean isMultiResolution() {
+ return mIsMultiResolution;
+ }
+
+ /**
* Get the physical camera ID associated with this {@link OutputConfiguration}.
*
* <p>If this OutputConfiguration isn't targeting a physical camera of a logical
diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
index cdcc92ce4404..365f870ba22d 100644
--- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java
@@ -212,7 +212,7 @@ public final class SharedSessionConfiguration {
}
public @Nullable String getPhysicalCameraId() {
- return mPhysicalCameraId;
+ return mPhysicalCameraId.isEmpty() ? null : mPhysicalCameraId;
}
}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 99f331f43450..de88895ba55c 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -99,8 +99,8 @@ public class HubEndpoint {
private final Object mLock = new Object();
private final HubEndpointInfo mPendingHubEndpointInfo;
- @Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
- @Nullable private final IHubEndpointMessageCallback mMessageCallback;
+ @Nullable private final HubEndpointLifecycleCallback mLifecycleCallback;
+ @Nullable private final HubEndpointMessageCallback mMessageCallback;
@NonNull private final Executor mLifecycleCallbackExecutor;
@NonNull private final Executor mMessageCallbackExecutor;
@@ -335,9 +335,9 @@ public class HubEndpoint {
private HubEndpoint(
@NonNull HubEndpointInfo pendingEndpointInfo,
- @Nullable IHubEndpointLifecycleCallback endpointLifecycleCallback,
+ @Nullable HubEndpointLifecycleCallback endpointLifecycleCallback,
@NonNull Executor lifecycleCallbackExecutor,
- @Nullable IHubEndpointMessageCallback endpointMessageCallback,
+ @Nullable HubEndpointMessageCallback endpointMessageCallback,
@NonNull Executor messageCallbackExecutor) {
mPendingHubEndpointInfo = pendingEndpointInfo;
mLifecycleCallback = endpointLifecycleCallback;
@@ -485,12 +485,12 @@ public class HubEndpoint {
}
@Nullable
- public IHubEndpointLifecycleCallback getLifecycleCallback() {
+ public HubEndpointLifecycleCallback getLifecycleCallback() {
return mLifecycleCallback;
}
@Nullable
- public IHubEndpointMessageCallback getMessageCallback() {
+ public HubEndpointMessageCallback getMessageCallback() {
return mMessageCallback;
}
@@ -498,11 +498,11 @@ public class HubEndpoint {
public static final class Builder {
private final String mPackageName;
- @Nullable private IHubEndpointLifecycleCallback mLifecycleCallback;
+ @Nullable private HubEndpointLifecycleCallback mLifecycleCallback;
@NonNull private Executor mLifecycleCallbackExecutor;
- @Nullable private IHubEndpointMessageCallback mMessageCallback;
+ @Nullable private HubEndpointMessageCallback mMessageCallback;
@NonNull private Executor mMessageCallbackExecutor;
private int mVersion;
@@ -539,10 +539,13 @@ public class HubEndpoint {
return this;
}
- /** Attach a callback interface for lifecycle events for this Endpoint */
+ /**
+ * Attach a callback interface for lifecycle events for this Endpoint. Callback will be
+ * posted to the main thread.
+ */
@NonNull
public Builder setLifecycleCallback(
- @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ @NonNull HubEndpointLifecycleCallback lifecycleCallback) {
mLifecycleCallback = lifecycleCallback;
return this;
}
@@ -554,15 +557,18 @@ public class HubEndpoint {
@NonNull
public Builder setLifecycleCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull IHubEndpointLifecycleCallback lifecycleCallback) {
+ @NonNull HubEndpointLifecycleCallback lifecycleCallback) {
mLifecycleCallbackExecutor = executor;
mLifecycleCallback = lifecycleCallback;
return this;
}
- /** Attach a callback interface for message events for this Endpoint */
+ /**
+ * Attach a callback interface for message events for this Endpoint. Callback will be posted
+ * to the main thread.
+ */
@NonNull
- public Builder setMessageCallback(@NonNull IHubEndpointMessageCallback messageCallback) {
+ public Builder setMessageCallback(@NonNull HubEndpointMessageCallback messageCallback) {
mMessageCallback = messageCallback;
return this;
}
@@ -574,7 +580,7 @@ public class HubEndpoint {
@NonNull
public Builder setMessageCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull IHubEndpointMessageCallback messageCallback) {
+ @NonNull HubEndpointMessageCallback messageCallback) {
mMessageCallbackExecutor = executor;
mMessageCallback = messageCallback;
return this;
diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java
index a61a7ebd0de9..4672bbb74170 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointDiscoveryCallback.java
@@ -30,7 +30,7 @@ import java.util.List;
*/
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointDiscoveryCallback {
+public interface HubEndpointDiscoveryCallback {
/**
* Called when a list of hub endpoints have started.
*
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java
index 698ed0adfd80..6d75010711cc 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointLifecycleCallback.java
@@ -29,7 +29,7 @@ import android.chre.flags.Flags;
*/
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointLifecycleCallback {
+public interface HubEndpointLifecycleCallback {
/**
* Called when an endpoint is requesting a session be opened with another endpoint.
*
diff --git a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java
index fde7017b5e76..5db54efc8893 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointMessageCallback.java
+++ b/core/java/android/hardware/contexthub/HubEndpointMessageCallback.java
@@ -26,18 +26,18 @@ import android.chre.flags.Flags;
* <p>This interface can be attached to an endpoint through {@link
* HubEndpoint.Builder#setMessageCallback} method. Methods in this interface will only be called
* when the endpoint is currently registered and has an open session. The endpoint will receive
- * session lifecycle callbacks through {@link IHubEndpointLifecycleCallback}.
+ * session lifecycle callbacks through {@link HubEndpointLifecycleCallback}.
*
* @hide
*/
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
-public interface IHubEndpointMessageCallback {
+public interface HubEndpointMessageCallback {
/**
* Callback interface for receiving messages for a particular endpoint session.
*
* @param session The session this message is sent through. Previously specified in a {@link
- * IHubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call.
+ * HubEndpointLifecycleCallback#onSessionOpened(HubEndpointSession)} call.
* @param message The {@link HubMessage} object representing a message received by the endpoint
* that registered this callback interface. This message is constructed by the
*/
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index 77f937ebeabc..f7f5636264e4 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -137,7 +137,7 @@ public class HubEndpointSession implements AutoCloseable {
* no service associated to this session.
*
* <p>For hub initiated sessions, the object was previously used in as an argument for open
- * request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
+ * request in {@link HubEndpointLifecycleCallback#onSessionOpenRequest}.
*
* <p>For app initiated sessions, the object was previously used in an open request in {@link
* android.hardware.location.ContextHubManager#openSession}
diff --git a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
index 1f2bdb985008..b193d00467aa 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSessionResult.java
@@ -23,7 +23,7 @@ import android.annotation.SystemApi;
import android.chre.flags.Flags;
/**
- * Return type of {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
+ * Return type of {@link HubEndpointLifecycleCallback#onSessionOpenRequest}. The value determines
* whether a open session request from the remote is accepted or not.
*
* @hide
diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java
index 2f33e8f8872b..651be3b17c33 100644
--- a/core/java/android/hardware/contexthub/HubServiceInfo.java
+++ b/core/java/android/hardware/contexthub/HubServiceInfo.java
@@ -112,6 +112,7 @@ public final class HubServiceInfo implements Parcelable {
* <p>The value can be one of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link
* HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}.
*/
+ @ServiceFormat
public int getFormat() {
return mFormat;
}
@@ -178,7 +179,8 @@ public final class HubServiceInfo implements Parcelable {
* <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService
* </ol>
*
- * @param serviceDescriptor The service descriptor.
+ * @param serviceDescriptor The service descriptor for the interface, provided by the
+ * vendor.
* @param format One of {@link HubServiceInfo#FORMAT_CUSTOM}, {@link
* HubServiceInfo#FORMAT_AIDL} or {@link HubServiceInfo#FORMAT_PW_RPC_PROTOBUF}.
* @param majorVersion Breaking changes should be a major version bump.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index ffa546067eff..9030810a1c1a 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -106,7 +106,7 @@ public final class DisplayManagerGlobal {
@IntDef(prefix = {"EVENT_DISPLAY_"}, flag = true, value = {
EVENT_DISPLAY_ADDED,
- EVENT_DISPLAY_CHANGED,
+ EVENT_DISPLAY_BASIC_CHANGED,
EVENT_DISPLAY_REMOVED,
EVENT_DISPLAY_BRIGHTNESS_CHANGED,
EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED,
@@ -119,7 +119,8 @@ public final class DisplayManagerGlobal {
public @interface DisplayEvent {}
public static final int EVENT_DISPLAY_ADDED = 1;
- public static final int EVENT_DISPLAY_CHANGED = 2;
+ public static final int EVENT_DISPLAY_BASIC_CHANGED = 2;
+
public static final int EVENT_DISPLAY_REMOVED = 3;
public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4;
public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5;
@@ -130,7 +131,7 @@ public final class DisplayManagerGlobal {
@LongDef(prefix = {"INTERNAL_EVENT_FLAG_"}, flag = true, value = {
INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
- INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+ INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
@@ -143,7 +144,7 @@ public final class DisplayManagerGlobal {
public @interface InternalEventFlag {}
public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0;
- public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1;
+ public static final long INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED = 1L << 1;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3;
public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4;
@@ -485,7 +486,7 @@ public final class DisplayManagerGlobal {
// There can be racing condition between DMS and WMS callbacks, so force triggering the
// listener to make sure the client can get the onDisplayChanged callback even if
// DisplayInfo is not changed (Display read from both DisplayInfo and WindowConfiguration).
- handleDisplayEvent(displayId, EVENT_DISPLAY_CHANGED, true /* forceUpdate */);
+ handleDisplayEvent(displayId, EVENT_DISPLAY_BASIC_CHANGED, true /* forceUpdate */);
}
private static Looper getLooperForHandler(@Nullable Handler handler) {
@@ -518,7 +519,8 @@ public final class DisplayManagerGlobal {
}
if (mDispatchNativeCallbacks) {
mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
if (!mTopologyListeners.isEmpty()) {
@@ -571,7 +573,8 @@ public final class DisplayManagerGlobal {
}
info = getDisplayInfoLocked(displayId);
- if (event == EVENT_DISPLAY_CHANGED && mDispatchNativeCallbacks) {
+ if ((event == EVENT_DISPLAY_BASIC_CHANGED
+ || event == EVENT_DISPLAY_REFRESH_RATE_CHANGED) && mDispatchNativeCallbacks) {
// Choreographer only supports a single display, so only dispatch refresh rate
// changes for the default display.
if (displayId == Display.DEFAULT_DISPLAY) {
@@ -1492,9 +1495,9 @@ public final class DisplayManagerGlobal {
mListener.onDisplayAdded(displayId);
}
break;
- case EVENT_DISPLAY_CHANGED:
- if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED)
- != 0) {
+ case EVENT_DISPLAY_BASIC_CHANGED:
+ if ((mInternalEventFlagsMask
+ & INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED) != 0) {
if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) {
if (extraLogging()) {
Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
@@ -1691,8 +1694,8 @@ public final class DisplayManagerGlobal {
switch (event) {
case EVENT_DISPLAY_ADDED:
return "ADDED";
- case EVENT_DISPLAY_CHANGED:
- return "CHANGED";
+ case EVENT_DISPLAY_BASIC_CHANGED:
+ return "BASIC_CHANGED";
case EVENT_DISPLAY_REMOVED:
return "REMOVED";
case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
@@ -1763,7 +1766,11 @@ public final class DisplayManagerGlobal {
}
if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED;
+ // For backward compatibility, a client subscribing to
+ // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
+ // RR changes
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
}
if ((eventFlags
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 211aefffa34c..ba5dfc094afb 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -129,14 +129,38 @@ public final class DisplayTopology implements Parcelable {
}
/**
+ * Update the size of a display and normalize the topology.
+ * @param displayId The logical display ID
+ * @param width The new width
+ * @param height The new height
+ * @return True if the topology has changed.
+ */
+ public boolean updateDisplay(int displayId, float width, float height) {
+ TreeNode display = findDisplay(displayId, mRoot);
+ if (display == null) {
+ return false;
+ }
+ if (floatEquals(display.mWidth, width) && floatEquals(display.mHeight, height)) {
+ return false;
+ }
+ display.mWidth = width;
+ display.mHeight = height;
+ normalize();
+ Slog.i(TAG, "Display with ID " + displayId + " updated, new width: " + width
+ + ", new height: " + height);
+ return true;
+ }
+
+ /**
* Remove a display from the topology.
* The default topology is created from the remaining displays, as if they were reconnected
* one by one.
* @param displayId The logical display ID
+ * @return True if the display was present in the topology and removed.
*/
- public void removeDisplay(int displayId) {
+ public boolean removeDisplay(int displayId) {
if (findDisplay(displayId, mRoot) == null) {
- return;
+ return false;
}
Queue<TreeNode> queue = new ArrayDeque<>();
queue.add(mRoot);
@@ -159,6 +183,7 @@ public final class DisplayTopology implements Parcelable {
} else {
Slog.i(TAG, "Display with ID " + displayId + " removed");
}
+ return true;
}
/**
@@ -685,12 +710,12 @@ public final class DisplayTopology implements Parcelable {
/**
* The width of the display in density-independent pixels (dp).
*/
- private final float mWidth;
+ private float mWidth;
/**
* The height of the display in density-independent pixels (dp).
*/
- private final float mHeight;
+ private float mHeight;
/**
* The position of this display relative to its parent.
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 313bad50e88e..c4d11cd8aff7 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -204,3 +204,10 @@ flag {
description: "Allows the user to disable input scrolling acceleration for mouse."
bug: "383555305"
}
+
+flag {
+ name: "remove_fallback_modifiers"
+ namespace: "input"
+ description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent."
+ bug: "382545048"
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 1e0cc94612dd..0cd320981c93 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -36,11 +36,11 @@ import android.content.pm.PackageManager;
import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubDiscoveryInfo;
import android.hardware.contexthub.HubEndpoint;
+import android.hardware.contexthub.HubEndpointDiscoveryCallback;
import android.hardware.contexthub.HubEndpointInfo;
+import android.hardware.contexthub.HubEndpointLifecycleCallback;
import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
-import android.hardware.contexthub.IHubEndpointDiscoveryCallback;
-import android.hardware.contexthub.IHubEndpointLifecycleCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -207,7 +207,7 @@ public final class ContextHubManager {
private Handler mCallbackHandler;
/** A map of endpoint discovery callbacks currently registered */
- private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
+ private Map<HubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
mDiscoveryCallbacks = new ConcurrentHashMap<>();
/**
@@ -718,7 +718,19 @@ public final class ContextHubManager {
/**
* Find a list of endpoints that provides a specific service.
*
- * @param serviceDescriptor Statically generated ID for an endpoint.
+ * <p>Service descriptor should uniquely identify the interface (scoped to type). Convention of
+ * the descriptor depend on interface type.
+ *
+ * <p>Examples:
+ *
+ * <ol>
+ * <li>AOSP-defined AIDL: android.hardware.something.IFoo/default
+ * <li>Vendor-defined AIDL: com.example.something.IBar/default
+ * <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService
+ * </ol>
+ *
+ * @param serviceDescriptor The service descriptor for a service provided by the hub. The value
+ * cannot be null or empty.
* @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery.
* @throws IllegalArgumentException if the serviceDescriptor is empty/null.
*/
@@ -750,14 +762,15 @@ public final class ContextHubManager {
/**
* Creates an interface to invoke endpoint discovery callbacks to send down to the service.
*
- * @param callback the callback to invoke at the client process
* @param executor the executor to invoke callbacks for this client
+ * @param callback the callback to invoke at the client process
+ * @param serviceDescriptor an optional descriptor to match discovery list with
* @return the callback interface
*/
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
- IHubEndpointDiscoveryCallback callback,
Executor executor,
+ HubEndpointDiscoveryCallback callback,
@Nullable String serviceDescriptor) {
return new IContextHubEndpointDiscoveryCallback.Stub() {
@Override
@@ -829,36 +842,36 @@ public final class ContextHubManager {
}
/**
- * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback,
- * Executor)} with the default executor in the main thread.
+ * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
+ * HubEndpointDiscoveryCallback, long)} with the default executor in the main thread.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void registerEndpointDiscoveryCallback(
- long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) {
+ @NonNull HubEndpointDiscoveryCallback callback, long endpointId) {
registerEndpointDiscoveryCallback(
- endpointId, callback, new HandlerExecutor(Handler.getMain()));
+ new HandlerExecutor(Handler.getMain()), callback, endpointId);
}
/**
* Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID
* has started or stopped.
*
- * @param endpointId The identifier of the hub endpoint.
- * @param callback The callback to be invoked.
* @param executor The executor to invoke the callback on.
+ * @param callback The callback to be invoked.
+ * @param endpointId The identifier of the hub endpoint.
* @throws UnsupportedOperationException If the operation is not supported.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void registerEndpointDiscoveryCallback(
- long endpointId,
- @NonNull IHubEndpointDiscoveryCallback callback,
- @NonNull Executor executor) {
- Objects.requireNonNull(callback, "callback cannot be null");
+ @NonNull Executor executor,
+ @NonNull HubEndpointDiscoveryCallback callback,
+ long endpointId) {
Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(callback, executor, null);
+ createDiscoveryCallback(executor, callback, null);
try {
mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
} catch (RemoteException e) {
@@ -869,42 +882,42 @@ public final class ContextHubManager {
}
/**
- * Equivalent to {@link #registerEndpointDiscoveryCallback(String,
- * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread.
+ * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
+ * HubEndpointDiscoveryCallback, String)} with the default executor in the main thread.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void registerEndpointDiscoveryCallback(
- @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) {
+ @NonNull HubEndpointDiscoveryCallback callback, @NonNull String serviceDescriptor) {
registerEndpointDiscoveryCallback(
- serviceDescriptor, callback, new HandlerExecutor(Handler.getMain()));
+ new HandlerExecutor(Handler.getMain()), callback, serviceDescriptor);
}
/**
* Registers a callback to be notified when the hub endpoint with the corresponding service
* descriptor has started or stopped.
*
+ * @param executor The executor to invoke the callback on.
* @param serviceDescriptor The service descriptor of the hub endpoint.
* @param callback The callback to be invoked.
- * @param executor The executor to invoke the callback on.
* @throws IllegalArgumentException if the serviceDescriptor is empty.
* @throws UnsupportedOperationException If the operation is not supported.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void registerEndpointDiscoveryCallback(
- @NonNull String serviceDescriptor,
- @NonNull IHubEndpointDiscoveryCallback callback,
- @NonNull Executor executor) {
- Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
- Objects.requireNonNull(callback, "callback cannot be null");
+ @NonNull Executor executor,
+ @NonNull HubEndpointDiscoveryCallback callback,
+ @NonNull String serviceDescriptor) {
Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
if (serviceDescriptor.isBlank()) {
throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
}
IContextHubEndpointDiscoveryCallback iCallback =
- createDiscoveryCallback(callback, executor, serviceDescriptor);
+ createDiscoveryCallback(executor, callback, serviceDescriptor);
try {
mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
} catch (RemoteException e) {
@@ -924,7 +937,7 @@ public final class ContextHubManager {
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public void unregisterEndpointDiscoveryCallback(
- @NonNull IHubEndpointDiscoveryCallback callback) {
+ @NonNull HubEndpointDiscoveryCallback callback) {
Objects.requireNonNull(callback, "callback cannot be null");
IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback);
if (iCallback == null) {
@@ -1291,7 +1304,7 @@ public final class ContextHubManager {
* service.
*
* <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
- * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+ * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
* object regarding the lifecycle events of the session.
*
* @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
@@ -1311,7 +1324,7 @@ public final class ContextHubManager {
* described by a {@link HubServiceInfo} object.
*
* <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
- * The registered endpoint will receive callbacks on its {@link IHubEndpointLifecycleCallback}
+ * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
* object regarding the lifecycle events of the session.
*
* @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 8a0adfaede8a..ecd90e46e432 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -480,10 +480,10 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
map.erase();
map.ensureCapacity(count);
}
- int numLazyValues = 0;
+ int[] numLazyValues = new int[]{0};
try {
- numLazyValues = parcelledData.readArrayMap(map, count, !parcelledByNative,
- /* lazy */ ownsParcel, this);
+ parcelledData.readArrayMap(map, count, !parcelledByNative,
+ /* lazy */ ownsParcel, this, numLazyValues);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -494,14 +494,14 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
} finally {
mWeakParcelledData = null;
if (ownsParcel) {
- if (numLazyValues == 0) {
+ if (numLazyValues[0] == 0) {
recycleParcel(parcelledData);
} else {
mWeakParcelledData = new WeakReference<>(parcelledData);
}
}
- mLazyValues = numLazyValues;
+ mLazyValues = numLazyValues[0];
mParcelledByNative = false;
mMap = map;
// Set field last as it is volatile
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 01222cdd38b3..18d2afb8b1b5 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -687,12 +687,18 @@ public final class BinderProxy implements IBinder {
return removeFrozenStateChangeCallbackNative(wrappedCallback);
}
+ public static boolean isFrozenStateChangeCallbackSupported() {
+ return isFrozenStateChangeCallbackSupportedNative();
+ }
+
private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback)
throws RemoteException;
private native boolean removeFrozenStateChangeCallbackNative(
FrozenStateChangeCallback callback);
+ private static native boolean isFrozenStateChangeCallbackSupportedNative();
+
/**
* Perform a dump on the remote object
*
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4bbc61c9f115..5ba6553a58c9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -5565,7 +5565,7 @@ public final class Parcel {
private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
int size, @Nullable ClassLoaderProvider loaderProvider) {
- readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider);
+ readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null);
}
/**
@@ -5575,18 +5575,17 @@ public final class Parcel {
* @param lazy Whether to populate the map with lazy {@link Function} objects for
* length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
* details.
- * @return a count of the lazy values in the map
+ * @param lazyValueCount number of lazy values added here
* @hide
*/
- int readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
- boolean lazy, @Nullable ClassLoaderProvider loaderProvider) {
- int lazyValues = 0;
+ void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
+ boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) {
while (size > 0) {
String key = readString();
Object value = (lazy) ? readLazyValue(loaderProvider) : readValue(
getClassLoader(loaderProvider));
if (value instanceof LazyValue) {
- lazyValues++;
+ lazyValueCount[0]++;
}
if (sorted) {
map.append(key, value);
@@ -5598,7 +5597,6 @@ public final class Parcel {
if (sorted) {
map.validate();
}
- return lazyValues;
}
/**
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index e2169925fdd3..289b98c9b1d4 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -41,6 +41,7 @@ public class TestLooperManager {
private boolean mReleased;
private boolean mLooperBlocked;
+ private final boolean mLooperIsMyLooper;
/**
* @hide
@@ -54,8 +55,11 @@ public class TestLooperManager {
}
mLooper = looper;
mQueue = mLooper.getQueue();
- // Post a message that will keep the looper blocked as long as we are dispatching.
- new Handler(looper).post(new LooperHolder());
+ mLooperIsMyLooper = Looper.myLooper() == looper;
+ if (!mLooperIsMyLooper) {
+ // Post a message that will keep the looper blocked as long as we are dispatching.
+ new Handler(looper).post(new LooperHolder());
+ }
}
/**
@@ -82,7 +86,7 @@ public class TestLooperManager {
public Message next() {
// Wait for the looper block to come up, to make sure we don't accidentally get
// the message for the block.
- while (!mLooperBlocked) {
+ while (!mLooperIsMyLooper && !mLooperBlocked) {
synchronized (this) {
try {
wait();
@@ -114,9 +118,6 @@ public class TestLooperManager {
* should be executed by this queue.
* If the queue is empty or no messages are deliverable, returns null.
* This method never blocks.
- *
- * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions
- * with it have completed.
*/
@FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
@SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value
@@ -165,6 +166,9 @@ public class TestLooperManager {
// This is being called from the thread it should be executed on, we can just dispatch.
message.target.dispatchMessage(message);
} else {
+ if (mLooperIsMyLooper) {
+ throw new RuntimeException("Cannot call execute from non Looper thread");
+ }
MessageExecution execution = new MessageExecution();
execution.m = message;
synchronized (execution) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 132805da7c94..507bcb8c2717 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -940,10 +940,10 @@ public class UserManager {
/**
* Specifies if a user is disallowed from resetting network settings
- * from Settings. This can only be set by device owners and profile owners on the primary user.
+ * from Settings. This can only be set by device owners and profile owners on the main user.
* The default value is <code>false</code>.
- * <p>This restriction has no effect on secondary users and managed profiles since only the
- * primary user can reset the network settings of the device.
+ * <p>This restriction has no effect on non-Admin users since they cannot reset the network
+ * settings of the device.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1077,11 +1077,11 @@ public class UserManager {
/**
* Specifies if a user is disallowed from configuring cell broadcasts.
*
- * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * <p>This restriction can only be set by a device owner, a profile owner on the main
* user or a profile owner of an organization-owned managed profile on the parent profile.
* When it is set by a device owner, it applies globally. When it is set by a profile owner
- * on the primary user or by a profile owner of an organization-owned managed profile on
- * the parent profile, it disables the primary user from configuring cell broadcasts.
+ * on the main user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the user from configuring cell broadcasts.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1089,8 +1089,8 @@ public class UserManager {
*
* <p>The default value is <code>false</code>.
*
- * <p>This restriction has no effect on secondary users and managed profiles since only the
- * primary user can configure cell broadcasts.
+ * <p>This restriction has no effect on non-Admin users since they cannot configure cell
+ * broadcasts.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -1103,11 +1103,11 @@ public class UserManager {
/**
* Specifies if a user is disallowed from configuring mobile networks.
*
- * <p>This restriction can only be set by a device owner, a profile owner on the primary
+ * <p>This restriction can only be set by a device owner, a profile owner on the main
* user or a profile owner of an organization-owned managed profile on the parent profile.
* When it is set by a device owner, it applies globally. When it is set by a profile owner
- * on the primary user or by a profile owner of an organization-owned managed profile on
- * the parent profile, it disables the primary user from configuring mobile networks.
+ * on the main user or by a profile owner of an organization-owned managed profile on
+ * the parent profile, it disables the user from configuring mobile networks.
*
* <p>Holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
@@ -1115,8 +1115,8 @@ public class UserManager {
*
* <p>The default value is <code>false</code>.
*
- * <p>This restriction has no effect on secondary users and managed profiles since only the
- * primary user can configure mobile networks.
+ * <p>This restriction has no effect on non-Admin users since they cannot configure mobile
+ * networks.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c3a49305af87..6898fcef23ab 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1296,6 +1296,22 @@ public final class Settings {
public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS";
/**
+ * Activity Action: Show settings of notifications on lockscreen.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_LOCKSCREEN_NOTIFICATIONS_SETTINGS =
+ "android.settings.LOCK_SCREEN_NOTIFICATIONS_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow pairing bluetooth devices.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index 90136ae00f6a..ffe8086ca4a1 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -93,6 +93,10 @@ import android.util.Log;
* must do its own state management (keeping in mind that the service's process might be killed
* by the Android System when unbound; for example, if the device is running low in memory).
*
+ * <p> The service also provides pending intents to override the system's Quick Access activities
+ * via the {@link #getTargetActivityPendingIntent} and the
+ * {@link #getGestureTargetActivityPendingIntent} method.
+ *
* <p>
* <a name="ErrorHandling"></a>
* <h3>Error handling</h3>
@@ -384,6 +388,10 @@ public abstract class QuickAccessWalletService extends Service {
*
* <p>The pending intent will be sent when the user performs a gesture to open Wallet.
* The pending intent should launch an activity.
+ *
+ * <p> If the gesture is performed and this method returns null, the system will launch the
+ * activity specified by the {@link #getTargetActivityPendingIntent} method. If that method
+ * also returns null, the system will launch the system-provided card switcher activity.
*/
@Nullable
@FlaggedApi(Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java
index 4f82800d1855..db5c57c49595 100644
--- a/core/java/android/service/settings/preferences/GetValueRequest.java
+++ b/core/java/android/service/settings/preferences/GetValueRequest.java
@@ -108,6 +108,7 @@ public final class GetValueRequest implements Parcelable {
/**
* Builder to construct {@link GetValueRequest}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
private final String mScreenKey;
private final String mPreferenceKey;
diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java
index 369dea77cc85..791131588034 100644
--- a/core/java/android/service/settings/preferences/GetValueResult.java
+++ b/core/java/android/service/settings/preferences/GetValueResult.java
@@ -170,6 +170,7 @@ public final class GetValueResult implements Parcelable {
/**
* Builder to construct {@link GetValueResult}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
@ResultCode
private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java
index ffecc6bec5b2..e0417152eedc 100644
--- a/core/java/android/service/settings/preferences/MetadataRequest.java
+++ b/core/java/android/service/settings/preferences/MetadataRequest.java
@@ -65,6 +65,7 @@ public final class MetadataRequest implements Parcelable {
/**
* Builder to construct {@link MetadataRequest}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
/** Constructs an immutable {@link MetadataRequest} object. */
@NonNull
diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java
index 6a65dcc9c757..e62fa8f38c93 100644
--- a/core/java/android/service/settings/preferences/MetadataResult.java
+++ b/core/java/android/service/settings/preferences/MetadataResult.java
@@ -131,6 +131,7 @@ public final class MetadataResult implements Parcelable {
/**
* Builder to construct {@link MetadataResult}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
@ResultCode
private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java
index f7600aecdfaf..77581d9deffe 100644
--- a/core/java/android/service/settings/preferences/SetValueRequest.java
+++ b/core/java/android/service/settings/preferences/SetValueRequest.java
@@ -123,6 +123,7 @@ public final class SetValueRequest implements Parcelable {
/**
* Builder to construct {@link SetValueRequest}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
private final String mScreenKey;
private final String mPreferenceKey;
diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java
index cb1776abd3bc..513f7a7d5bcc 100644
--- a/core/java/android/service/settings/preferences/SetValueResult.java
+++ b/core/java/android/service/settings/preferences/SetValueResult.java
@@ -156,6 +156,7 @@ public final class SetValueResult implements Parcelable {
/**
* Builder to construct {@link SetValueResult}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
@ResultCode
private final int mResultCode;
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
index ea7d4a675713..30631f2fd71d 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
@@ -102,6 +102,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
/**
* Returns the breadcrumbs (navigation context) of Preference.
* <p>May be empty.
+ * @hide restrict to platform; may be opened wider in the future
*/
@NonNull
public List<String> getBreadcrumbs() {
@@ -189,33 +190,32 @@ public final class SettingsPreferenceMetadata implements Parcelable {
@IntDef(value = {
NO_SENSITIVITY,
EXPECT_POST_CONFIRMATION,
- EXPECT_PRE_CONFIRMATION,
+ DEEPLINK_ONLY,
NO_DIRECT_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WriteSensitivity {}
/**
- * Indicates preference is not sensitive.
+ * Indicates preference is not write-sensitive.
* <p>Its value is writable without explicit consent, assuming all necessary permissions are
* granted.
*/
public static final int NO_SENSITIVITY = 0;
/**
- * Indicates preference is mildly sensitive.
+ * Indicates preference is mildly write-sensitive.
* <p>In addition to necessary permissions, after writing its value the user should be
* given the option to revert back.
*/
public static final int EXPECT_POST_CONFIRMATION = 1;
/**
- * Indicates preference is sensitive.
- * <p>In addition to necessary permissions, the user should be prompted for confirmation prior
- * to making a change. Otherwise it is suggested to provide a deeplink to the Preference's page
- * instead, accessible via {@link #getLaunchIntent}.
+ * Indicates preference is write-sensitive.
+ * <p>This preference cannot be changed through this API; instead a deeplink to the Preference's
+ * page should be used instead, accessible via {@link #getLaunchIntent}.
*/
- public static final int EXPECT_PRE_CONFIRMATION = 2;
+ public static final int DEEPLINK_ONLY = 2;
/**
- * Indicates preference is highly sensitivity and carries significant user-risk.
+ * Indicates preference is highly write-sensitivity and carries significant user-risk.
* <p>This Preference cannot be changed through this API and no direct deeplink is available.
* Other Metadata is still available.
*/
@@ -303,6 +303,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
/**
* Builder to construct {@link SettingsPreferenceMetadata}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
private final String mScreenKey;
private final String mKey;
@@ -355,6 +356,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
/**
* Sets the preference breadcrumbs (navigation context).
+ * @hide
*/
@NonNull
public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) {
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
index 08826ca9776b..eea93b321e47 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -170,6 +170,7 @@ public final class SettingsPreferenceValue implements Parcelable {
/**
* Builder to construct {@link SettingsPreferenceValue}.
*/
+ @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public static final class Builder {
@Type
private final int mType;
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index fb1bd1703ce6..6116d599baa0 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -70,3 +70,11 @@ flag {
is_fixed_read_only: true
bug: "352538294"
}
+
+flag {
+ name: "system_server_large_perfetto_shmem_buffer"
+ namespace: "windowing_tools"
+ description: "Large perfetto shmem buffer"
+ is_fixed_read_only: true
+ bug: "382369925"
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 089b5c256b6e..6c50b5f945a5 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.flags.Flags.bufferStuffingRecovery;
import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
@@ -965,22 +966,24 @@ public final class Choreographer {
// Evaluate if buffer stuffing recovery needs to start or end, and
// what actions need to be taken for recovery.
- switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) {
- case NONE:
- // Without buffer stuffing recovery, offsetFrameTimeNanos is
- // synonymous with frameTimeNanos.
- break;
- case OFFSET:
- // Add animation offset. Used to update frame timeline with
- // offset before jitter is calculated.
- offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
- break;
- case DELAY_FRAME:
- // Intentional frame delay to help reduce queued buffer count.
- scheduleVsyncLocked();
- return;
- default:
- break;
+ if (bufferStuffingRecovery()) {
+ switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) {
+ case NONE:
+ // Without buffer stuffing recovery, offsetFrameTimeNanos is
+ // synonymous with frameTimeNanos.
+ break;
+ case OFFSET:
+ // Add animation offset. Used to update frame timeline with
+ // offset before jitter is calculated.
+ offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
+ break;
+ case DELAY_FRAME:
+ // Intentional frame delay to help reduce queued buffer count.
+ scheduleVsyncLocked();
+ return;
+ default:
+ break;
+ }
}
try {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0c8a0d60a96a..ca0959af3ff8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1597,7 +1597,9 @@ public final class Display {
// Although we only care about the HDR/SDR ratio changing, that can also come in the
// form of the larger DISPLAY_CHANGED event
mGlobal.registerDisplayListener(toRegister, executor,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ DisplayManagerGlobal
+ .INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal
.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
ActivityThread.currentPackageName());
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index ba098eb53246..e75b1b0bd17a 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -447,7 +447,18 @@ public final class DisplayInfo implements Parcelable {
}
public boolean equals(DisplayInfo other) {
- return other != null
+ return equals(other, /* compareRefreshRate */ true);
+ }
+
+ /**
+ * Compares if the two DisplayInfo objects are equal or not
+ * @param other The other DisplayInfo against which the comparison is to be done
+ * @param compareRefreshRate Indicates if the refresh rate is also to be considered in
+ * comparison
+ * @return
+ */
+ public boolean equals(DisplayInfo other, boolean compareRefreshRate) {
+ boolean isEqualWithoutRefreshRate = other != null
&& layerStack == other.layerStack
&& flags == other.flags
&& type == other.type
@@ -466,7 +477,6 @@ public final class DisplayInfo implements Parcelable {
&& logicalHeight == other.logicalHeight
&& Objects.equals(displayCutout, other.displayCutout)
&& rotation == other.rotation
- && modeId == other.modeId
&& hasArrSupport == other.hasArrSupport
&& Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
&& Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)
@@ -490,7 +500,6 @@ public final class DisplayInfo implements Parcelable {
&& ownerUid == other.ownerUid
&& Objects.equals(ownerPackageName, other.ownerPackageName)
&& removeMode == other.removeMode
- && getRefreshRate() == other.getRefreshRate()
&& brightnessMinimum == other.brightnessMinimum
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
@@ -504,6 +513,13 @@ public final class DisplayInfo implements Parcelable {
&& Objects.equals(
thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId)
&& canHostTasks == other.canHostTasks;
+
+ if (compareRefreshRate) {
+ return isEqualWithoutRefreshRate
+ && (getRefreshRate() == other.getRefreshRate())
+ && (modeId == other.modeId);
+ }
+ return isEqualWithoutRefreshRate;
}
@Override
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index a8d4e2d2c70a..48dfdd4a95f4 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,6 +16,9 @@
package android.view;
+
+import static com.android.hardware.input.Flags.removeFallbackModifiers;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -458,7 +461,15 @@ public class KeyCharacterMap implements Parcelable {
FallbackAction action = FallbackAction.obtain();
metaState = KeyEvent.normalizeMetaState(metaState);
if (nativeGetFallbackAction(mPtr, keyCode, metaState, action)) {
- action.metaState = KeyEvent.normalizeMetaState(action.metaState);
+ if (removeFallbackModifiers()) {
+ // Strip all modifiers. This is safe to do since only exact keyCode + metaState
+ // modifiers will trigger a fallback.
+ // E.g. Ctrl + Space -> language_switch (fallback generated)
+ // Ctrl + Alt + Space -> Ctrl + Alt + Space (no fallback generated)
+ action.metaState = 0;
+ } else {
+ action.metaState = KeyEvent.normalizeMetaState(action.metaState);
+ }
return action;
}
action.recycle();
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6e6e87bb9403..4fc1cfc0ca82 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -206,7 +206,8 @@ public class Surface implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
- FRAME_RATE_COMPATIBILITY_GTE})
+ FRAME_RATE_COMPATIBILITY_AT_LEAST, FRAME_RATE_COMPATIBILITY_EXACT,
+ FRAME_RATE_COMPATIBILITY_MIN})
public @interface FrameRateCompatibility {}
// From native_window.h. Keep these in sync.
@@ -219,7 +220,7 @@ public class Surface implements Parcelable {
* In Android version {@link Build.VERSION_CODES#BAKLAVA} and above, use
* {@link FRAME_RATE_COMPATIBILITY_DEFAULT} for game content.
* For other cases, see {@link FRAME_RATE_COMPATIBILITY_FIXED_SOURCE} and
- * {@link FRAME_RATE_COMPATIBILITY_GTE}.
+ * {@link FRAME_RATE_COMPATIBILITY_AT_LEAST}.
*/
public static final int FRAME_RATE_COMPATIBILITY_DEFAULT = 0;
@@ -234,7 +235,7 @@ public class Surface implements Parcelable {
public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
/**
- * The surface requests a frame rate that is greater than or equal to the specified frame rate.
+ * The surface requests a frame rate that is at least the specified frame rate.
* This value should be used for UIs, animations, scrolling and fling, and anything that is not
* a game or video.
*
@@ -242,7 +243,7 @@ public class Surface implements Parcelable {
* {@link FRAME_RATE_COMPATIBILITY_DEFAULT}.
*/
@FlaggedApi(com.android.graphics.surfaceflinger.flags.Flags.FLAG_ARR_SETFRAMERATE_GTE_ENUM)
- public static final int FRAME_RATE_COMPATIBILITY_GTE = 2;
+ public static final int FRAME_RATE_COMPATIBILITY_AT_LEAST = 2;
/**
* This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f22505b80948..833f2d98554e 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.flags.Flags.bufferStuffingRecovery;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -5118,11 +5117,9 @@ public final class SurfaceControl implements Parcelable {
*/
@NonNull
public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
- if (bufferStuffingRecovery()) {
- checkPreconditions(sc);
- nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
- RECOVERABLE_FROM_BUFFER_STUFFING);
- }
+ checkPreconditions(sc);
+ nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
+ RECOVERABLE_FROM_BUFFER_STUFFING);
return this;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d13f0e21bf80..d88b6d642ee6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27,7 +27,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
@@ -34199,7 +34199,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
&& viewRootImpl.shouldCheckFrameRateCategory()
&& parent instanceof View
&& ((View) parent).getFrameContentVelocity() <= 0
- && !isInputMethodWindowType) {
+ && !isInputMethodWindowType
+ && viewRootImpl.getFrameRateCompatibility() != FRAME_RATE_COMPATIBILITY_AT_LEAST) {
return FRAME_RATE_CATEGORY_HIGH_HINT | FRAME_RATE_CATEGORY_REASON_BOOST;
}
@@ -34251,7 +34252,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
frameRateToSet = frameRate;
} else {
- compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+ compatibility = FRAME_RATE_COMPATIBILITY_AT_LEAST;
frameRateToSet = velocityFrameRate;
}
viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1d27574eca8c..16cdb64f62cc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -38,7 +38,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
@@ -1828,7 +1828,8 @@ public final class ViewRootImpl implements ViewParent,
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED
: DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
DisplayManagerGlobal
.getInstance()
@@ -13271,7 +13272,7 @@ public final class ViewRootImpl implements ViewParent,
* We set category to HIGH if the maximum frame rate is greater than 60.
* Otherwise, we set category to NORMAL.
*
- * Use FRAME_RATE_COMPATIBILITY_GTE for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
+ * Use FRAME_RATE_COMPATIBILITY_AT_LEAST for velocity and FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
* for TextureView video play and user requested frame rate.
*
* @param frameRate the preferred frame rate of a View
@@ -13282,7 +13283,7 @@ public final class ViewRootImpl implements ViewParent,
if (frameRate <= 0) {
return;
}
- if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && !mIsPressedGesture) {
+ if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_AT_LEAST && !mIsPressedGesture) {
mIsTouchBoosting = false;
mIsFrameRateBoosting = false;
if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index 70c899f1efc7..8baa55f8e377 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -142,6 +142,11 @@ final class ChildContentCaptureSession extends ContentCaptureSession {
}
@Override
+ void internalNotifySessionFlushEvent(int sessionId) {
+ getMainCaptureSession().internalNotifySessionFlushEvent(sessionId);
+ }
+
+ @Override
boolean isContentCaptureEnabled() {
return getMainCaptureSession().isContentCaptureEnabled();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index db4ac5de0b49..efd39163c3b8 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -18,7 +18,9 @@ package android.view.contentcapture;
import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
import static android.view.contentcapture.ContentCaptureManager.DEBUG;
import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
+import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -137,6 +139,12 @@ public final class ContentCaptureEvent implements Parcelable {
*/
public static final int TYPE_WINDOW_BOUNDS_CHANGED = 10;
+ /**
+ * Called to flush a semantics meaningful view changes status to Intelligence Service.
+ */
+ @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED)
+ public static final int TYPE_SESSION_FLUSH = 11;
+
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_VIEW_APPEARED,
@@ -149,6 +157,7 @@ public final class ContentCaptureEvent implements Parcelable {
TYPE_SESSION_RESUMED,
TYPE_VIEW_INSETS_CHANGED,
TYPE_WINDOW_BOUNDS_CHANGED,
+ TYPE_SESSION_FLUSH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType{}
@@ -697,6 +706,8 @@ public final class ContentCaptureEvent implements Parcelable {
return "VIEW_INSETS_CHANGED";
case TYPE_WINDOW_BOUNDS_CHANGED:
return "TYPE_WINDOW_BOUNDS_CHANGED";
+ case TYPE_SESSION_FLUSH:
+ return "TYPE_SESSION_FLUSH";
default:
return "UKNOWN_TYPE: " + type;
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 0ca36ba28e3a..9aeec20ec9b7 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -19,8 +19,10 @@ import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureManager.NO_SESSION_ID;
+import static android.view.contentcapture.flags.Flags.FLAG_CCAPI_BAKLAVA_ENABLED;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -548,6 +550,35 @@ public abstract class ContentCaptureSession implements AutoCloseable {
abstract void internalNotifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets);
+ /**
+ * Flushes an internal buffer of UI events and signals System Intelligence (SI) that a
+ * semantically meaningful state has been reached. SI uses this signal to potentially
+ * rebuild the view hierarchy and understand the current state of the UI.
+ *
+ * <p>UI events are often batched together for performance reasons. A semantic batch
+ * represents a series of events that, when applied sequentially, result in a
+ * meaningful and complete UI state.
+ *
+ * <p>It is crucial to call {@code flush()} after completing a semantic batch to ensure
+ * SI can accurately reconstruct the view hierarchy.
+ *
+ * <p><b>Premature Flushing:</b> Calling {@code flush()} within a semantic batch may
+ * lead to SI failing to rebuild the view hierarchy correctly. This could manifest as
+ * incorrect ordering of sibling nodes.
+ *
+ * <p><b>Delayed Flushing:</b> While not immediately flushing after a semantic batch is
+ * generally safe, it's recommended to do so as soon as possible. In the worst-case
+ * scenario where a {@code flush()} is never called, SI will attempt to process the
+ * events after a short delay based on view appearance and disappearance events.
+ */
+ @FlaggedApi(FLAG_CCAPI_BAKLAVA_ENABLED)
+ public void flush() {
+ internalNotifySessionFlushEvent(mId);
+ }
+
+ /** @hide */
+ abstract void internalNotifySessionFlushEvent(int sessionId);
+
/** @hide */
public void notifyViewTreeEvent(boolean started) {
internalNotifyViewTreeEvent(mId, started);
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index eb827dd5258d..2fb78c038ca2 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -17,6 +17,7 @@ package android.view.contentcapture;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FLUSH;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
@@ -623,6 +624,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
public void flush(@FlushReason int reason) {
+ // TODO: b/380381249 renaming the internal APIs to prevent confusions between this and the
+ // public API.
runOnContentCaptureThread(() -> flushImpl(reason));
}
@@ -890,6 +893,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
enqueueEvent(event);
}
+ @Override
+ void internalNotifySessionFlushEvent(int sessionId) {
+ final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_SESSION_FLUSH);
+ enqueueEvent(event, FORCE_FLUSH);
+ }
+
private List<ContentCaptureEvent> clearBufferEvents() {
final ArrayList<ContentCaptureEvent> bufferEvents = new ArrayList<>();
ContentCaptureEvent event;
diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS
index 9ac273f515e7..30f4cae4bf19 100644
--- a/core/java/android/view/contentcapture/OWNERS
+++ b/core/java/android/view/contentcapture/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 544200
-hackz@google.com
-shivanker@google.com
+dariofreni@google.com
+klikli@google.com
+shikhamalhotra@google.com
diff --git a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
index 416a877d87ab..f709ed7f57cd 100644
--- a/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
+++ b/core/java/android/view/contentcapture/flags/content_capture_flags.aconfig
@@ -7,3 +7,10 @@ flag {
description: "Feature flag for running content capture tasks on background thread"
bug: "309411951"
}
+
+flag {
+ name: "ccapi_baklava_enabled"
+ namespace: "machine_learning"
+ description: "Feature flag for baklava content capture API"
+ bug: "380381249"
+}
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index 11019324acd8..fcd7dfbb1769 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -148,6 +148,9 @@ public class WindowTokenClientController {
info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed attachToDisplayContent", e);
+ return false;
}
if (info == null) {
return false;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 801698caff0e..b97bf0b54c1a 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -2,10 +2,10 @@ package: "com.android.window.flags"
container: "system"
flag {
- name: "disable_thin_letterboxing_policy"
+ name: "ignore_aspect_ratio_restrictions_for_resizeable_freeform_activities"
namespace: "large_screen_experiences_app_compat"
- description: "Whether reachability is disabled in case of thin letterboxing"
- bug: "341027847"
+ description: "If a resizeable activity enters freeform mode, ignore all aspect ratio constraints."
+ bug: "381866902"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -58,13 +58,6 @@ flag {
}
flag {
- name: "user_min_aspect_ratio_app_default"
- namespace: "large_screen_experiences_app_compat"
- description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
- bug: "310816437"
-}
-
-flag {
name: "allow_hide_scm_button"
namespace: "large_screen_experiences_app_compat"
description: "Whether we should allow hiding the size compat restart button"
@@ -80,16 +73,6 @@ flag {
}
flag {
- name: "immersive_app_repositioning"
- namespace: "large_screen_experiences_app_compat"
- description: "Fix immersive apps changing size when repositioning"
- bug: "334076352"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "camera_compat_for_freeform"
namespace: "large_screen_experiences_app_compat"
description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 9d11d149b0ed..c1ed51264e23 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,17 +368,6 @@ flag {
}
flag {
- name: "disallow_app_progress_embedded_window"
- namespace: "windowing_frontend"
- description: "Pilfer pointers when app transfer input gesture to embedded window."
- bug: "365504126"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "predictive_back_system_override_callback"
namespace: "windowing_frontend"
description: "Provide pre-make predictive back API extension"
@@ -430,4 +419,15 @@ flag {
description: "Support insets definition and calculation relative to task bounds."
bug: "277292497"
is_fixed_read_only: true
+}
+
+flag {
+ name: "exclude_drawing_app_theme_snapshot_from_lock"
+ namespace: "windowing_frontend"
+ description: "Do not hold wm lock when drawing app theme snapshot."
+ is_fixed_read_only: true
+ bug: "373502791"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ae846441723a..abd93cfaf179 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -105,6 +105,13 @@ flag {
flag {
namespace: "windowing_sdk"
+ name: "activity_embedding_support_for_connected_displays"
+ description: "Enables activity embedding support for connected displays, including enabling AE optimization for Settings."
+ bug: "369438353"
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "wlinfo_oncreate"
description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()"
bug: "337820752"
@@ -148,3 +155,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "condense_configuration_change_for_simple_mode"
+ description: "Condense configuration change for simple mode"
+ bug: "356738240"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 2cfc680a3fe8..f01aa80fab4f 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -163,4 +163,5 @@ interface IAppOpsService {
void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName,
@nullable String attributionTag, int virtualDeviceId);
List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId);
+ oneway void noteOperationsInBatch(in Map batchedNoteOps);
}
diff --git a/core/java/com/android/internal/app/ProcessMap.java b/core/java/com/android/internal/app/ProcessMap.java
index 542b6d00ca37..b4945e7fd2ec 100644
--- a/core/java/com/android/internal/app/ProcessMap.java
+++ b/core/java/com/android/internal/app/ProcessMap.java
@@ -28,6 +28,11 @@ public class ProcessMap<E> {
if (uids == null) return null;
return uids.get(uid);
}
+
+ public SparseArray<E> get(String name) {
+ SparseArray<E> uids = mMap.get(name);
+ return uids;
+ }
public E put(String name, int uid, E value) {
SparseArray<E> uids = mMap.get(name);
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 0c2fd4bbd7ae..5d66b3c10197 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -148,8 +148,8 @@ public class DisplayResolutionTracker {
public void registerDisplayListener(DisplayManager.DisplayListener listener) {
manager.registerDisplayListener(listener, handler,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal
- .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
ActivityThread.currentPackageName());
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 3e2f30118b2a..f14e1f63cdf6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -236,4 +236,7 @@ interface IStatusBarService
/** Shows rear display educational dialog */
void showRearDisplayDialog(int currentBaseState);
+
+ /** Unbundle a categorized notification */
+ void unbundleNotification(String key);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9bd52372e6c4..39ddea614ee4 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,6 +25,8 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
import static android.security.Flags.reportPrimaryAuthAttempts;
import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
+import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,6 +44,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.hardware.input.InputManagerGlobal;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -59,6 +62,7 @@ import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
+import android.view.InputDevice;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
@@ -1097,12 +1101,20 @@ public class LockPatternUtils {
return type == CREDENTIAL_TYPE_PATTERN;
}
+ private boolean hasActivePointerDeviceAttached() {
+ return !getEnabledNonTouchInputDevices(InputDevice.SOURCE_CLASS_POINTER).isEmpty();
+ }
+
/**
* @return Whether the visible pattern is enabled.
*/
@UnsupportedAppUsage
public boolean isVisiblePatternEnabled(int userId) {
- return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId);
+ boolean defaultValue = true;
+ if (hideLastCharWithPhysicalInput()) {
+ defaultValue = !hasActivePointerDeviceAttached();
+ }
+ return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, defaultValue, userId);
}
/**
@@ -1116,11 +1128,39 @@ public class LockPatternUtils {
return getString(Settings.Secure.LOCK_PATTERN_VISIBLE, userId) != null;
}
+ private List<InputDevice> getEnabledNonTouchInputDevices(int source) {
+ final InputManagerGlobal inputManager = InputManagerGlobal.getInstance();
+ final int[] inputIds = inputManager.getInputDeviceIds();
+ List<InputDevice> matchingDevices = new ArrayList<InputDevice>();
+ for (final int deviceId : inputIds) {
+ final InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (!inputDevice.isEnabled()) continue;
+ if (inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN)) continue;
+ if (inputDevice.isVirtual()) continue;
+ if (!inputDevice.supportsSource(source)) continue;
+ matchingDevices.add(inputDevice);
+ }
+ return matchingDevices;
+ }
+
+ private boolean hasPhysicalKeyboardActive() {
+ final List<InputDevice> keyboards =
+ getEnabledNonTouchInputDevices(InputDevice.SOURCE_KEYBOARD);
+ for (final InputDevice keyboard : keyboards) {
+ if (keyboard.isFullKeyboard()) return true;
+ }
+ return false;
+ }
+
/**
* @return Whether enhanced pin privacy is enabled.
*/
public boolean isPinEnhancedPrivacyEnabled(int userId) {
- return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, false, userId);
+ boolean defaultValue = false;
+ if (hideLastCharWithPhysicalInput()) {
+ defaultValue = hasPhysicalKeyboardActive();
+ }
+ return getBoolean(LOCK_PIN_ENHANCED_PRIVACY, defaultValue, userId);
}
/**
diff --git a/core/jni/android_hardware_camera2_CameraDevice.cpp b/core/jni/android_hardware_camera2_CameraDevice.cpp
index 493c7073416c..04cfed581750 100644
--- a/core/jni/android_hardware_camera2_CameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_CameraDevice.cpp
@@ -30,6 +30,7 @@
#include <nativehelper/JNIHelp.h>
#include "android_os_Parcel.h"
#include "core_jni_helpers.h"
+#include <android/binder_auto_utils.h>
#include <android/binder_parcel_jni.h>
#include <android/hardware/camera2/ICameraDeviceUser.h>
#include <aidl/android/hardware/common/fmq/MQDescriptor.h>
@@ -40,6 +41,7 @@
using namespace android;
using ::android::AidlMessageQueue;
+using ndk::ScopedAParcel;
using ResultMetadataQueue = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
class FMQReader {
@@ -75,15 +77,16 @@ extern "C" {
static jlong CameraDevice_createFMQReader(JNIEnv *env, jclass thiz,
jobject resultParcel) {
- AParcel *resultAParcel = AParcel_fromJavaParcel(env, resultParcel);
- if (resultAParcel == nullptr) {
+ ScopedAParcel sResultAParcel(AParcel_fromJavaParcel(env, resultParcel));
+ if (sResultAParcel.get() == nullptr) {
ALOGE("%s: Error creating result parcel", __FUNCTION__);
return 0;
}
- AParcel_setDataPosition(resultAParcel, 0);
+
+ AParcel_setDataPosition(sResultAParcel.get(), 0);
MQDescriptor<int8_t, SynchronizedReadWrite> resultMQ;
- if (resultMQ.readFromParcel(resultAParcel) != OK) {
+ if (resultMQ.readFromParcel(sResultAParcel.get()) != OK) {
ALOGE("%s: read from result parcel failed", __FUNCTION__);
return 0;
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 8003bb7d442b..639f5bff7614 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1706,6 +1706,10 @@ static jboolean android_os_BinderProxy_removeFrozenStateChangeCallback(JNIEnv* e
return res;
}
+static jboolean android_os_BinderProxy_frozenStateChangeCallbackSupported(JNIEnv*, jclass*) {
+ return ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+}
+
static void BinderProxy_destroy(void* rawNativeData)
{
BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData;
@@ -1750,6 +1754,8 @@ static const JNINativeMethod gBinderProxyMethods[] = {
"(Landroid/os/IBinder$FrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
{"removeFrozenStateChangeCallbackNative",
"(Landroid/os/IBinder$FrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
+ {"isFrozenStateChangeCallbackSupportedNative",
+ "()Z", (void*)android_os_BinderProxy_frozenStateChangeCallbackSupported},
{"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
{"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index df989527efe4..ed05e6de0fe0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8780,6 +8780,20 @@
android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
<!--
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_UID_BASED_PROVIDER_LOOKUP)
+ Allows an app to resolve components (e.g ContentProviders) on behalf of
+ other UIDs
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission
+ android:name="android.permission.RESOLVE_COMPONENT_FOR_UID"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.content.pm.uid_based_provider_lookup" />
+ <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+
+ <!--
@TestApi
Signature permission reserved for testing. This should never be used to
gate any actual functionality.
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 76c810bdb2c1..e91e1115ac1c 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -157,39 +157,27 @@
android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
+ android:minWidth="@dimen/notification_content_margin_end"
>
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:minWidth="@dimen/notification_content_margin_end"
- >
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
- </LinearLayout>
+ </FrameLayout>
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 2e0a7afc3cd1..2d367337bb6f 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -194,4 +194,11 @@
</FrameLayout>
</LinearLayout>
+
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index f644adefda9d..fbecb8c30b9c 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -199,6 +199,12 @@
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
<LinearLayout
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index fc727e1c72f5..2d30d8a8bbb6 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -60,7 +60,7 @@
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_toStartOf="@id/notification_buttons_column"
+ android:layout_toStartOf="@id/expand_button"
android:layout_alignWithParentIfMissing="true"
android:clipChildren="false"
android:gravity="center_vertical"
@@ -81,28 +81,17 @@
android:focusable="false"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
- >
-
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- />
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true" />
- </LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
</NotificationHeaderView>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index e28b6462bad7..e6295ea06177 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -100,4 +100,7 @@
<!-- Whether to enable scaling and fading animation to scrollviews while scrolling.
P.S this is a change only intended for wear devices. -->
<bool name="config_enableViewGroupScalingFading">true</bool>
+
+ <!-- Allow the gesture to double tap the power button to trigger a target action. -->
+ <bool name="config_doubleTapPowerGestureEnabled">false</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 565e28e0cc87..45a5d85a097d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2165,6 +2165,17 @@
config_enableGeofenceOverlay is false. -->
<string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+ <!-- Whether to enable GNSS assistance overlay which allows GnssAssistanceProvider to be
+ replaced by an app at run-time. When disabled, only the
+ config_gnssAssistanceProviderPackageName package will be searched for
+ GnssAssistanceProvider, otherwise any system package is eligible. Anyone who wants to
+ disable the overlay mechanism can set it to false.
+ -->
+ <bool name="config_enableGnssAssistanceOverlay" translatable="false">true</bool>
+ <!-- Package name providing GNSS assistance API support. Used only when
+ config_enableGnssAssistanceOverlay is false. -->
+ <string name="config_gnssAssistanceProviderPackageName" translatable="false">@null</string>
+
<!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
Activity-Recognition to be replaced by an app at run-time. When disabled, only the
config_activityRecognitionHardwarePackageName package will be searched for
diff --git a/core/res/res/values/config_watch.xml b/core/res/res/values/config_watch.xml
index 629a343f1280..bcb1e0941b5a 100644
--- a/core/res/res/values/config_watch.xml
+++ b/core/res/res/values/config_watch.xml
@@ -15,8 +15,7 @@
-->
<resources>
- <!-- TODO(b/382103556): use predefined Material3 token -->
<!-- For Wear Material3 -->
- <dimen name="config_wearMaterial3_buttonCornerRadius">26dp</dimen>
- <dimen name="config_wearMaterial3_bottomDialogCornerRadius">18dp</dimen>
+ <dimen name="config_wearMaterial3_buttonCornerRadius">@dimen/config_shapeCornerRadiusLarge</dimen>
+ <dimen name="config_wearMaterial3_bottomDialogCornerRadius">@dimen/config_shapeCornerRadiusMedium</dimen>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 73e06f6a2520..8195d38993c8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2040,6 +2040,7 @@
<java-symbol type="bool" name="config_useGnssHardwareProvider" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
<java-symbol type="bool" name="config_enableGeofenceOverlay" />
+ <java-symbol type="bool" name="config_enableGnssAssistanceOverlay" />
<java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
<java-symbol type="bool" name="config_sf_limitedAlpha" />
<java-symbol type="bool" name="config_unplugTurnsOnScreen" />
@@ -2223,6 +2224,7 @@
<java-symbol type="string" name="config_gnssLocationProviderPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
<java-symbol type="string" name="config_geofenceProviderPackageName" />
+ <java-symbol type="string" name="config_gnssAssistanceProviderPackageName" />
<java-symbol type="string" name="config_networkLocationProviderPackageName" />
<java-symbol type="string" name="config_wimaxManagerClassname" />
<java-symbol type="string" name="config_wimaxNativeLibLocation" />
diff --git a/core/tests/coretests/res/color/color_with_lstar.xml b/core/tests/coretests/res/color/color_with_lstar.xml
index dcc3d6db1b0a..7762fc069ed5 100644
--- a/core/tests/coretests/res/color/color_with_lstar.xml
+++ b/core/tests/coretests/res/color/color_with_lstar.xml
@@ -16,5 +16,5 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="#ff0000" android:lStar="50" />
+ <item android:color="@color/testcolor2" android:lStar="50" />
</selector>
diff --git a/core/tests/coretests/res/values/colors.xml b/core/tests/coretests/res/values/colors.xml
index 029aa0dd8eb6..f01af8421515 100644
--- a/core/tests/coretests/res/values/colors.xml
+++ b/core/tests/coretests/res/values/colors.xml
@@ -25,6 +25,5 @@
<drawable name="yellow">#ffffff00</drawable>
<color name="testcolor1">#ff00ff00</color>
<color name="testcolor2">#ffff0000</color>
- <color name="testcolor3">#fff00000</color>
<color name="failColor">#ff0000ff</color>
</resources>
diff --git a/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
index cf6266c756ce..931d64640ea2 100644
--- a/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
+++ b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
@@ -119,4 +119,15 @@ public class BackgroundStartPrivilegesTest {
Arrays.asList(BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A)))
.isEqualTo(BSP_ALLOW_A);
}
+
+ @Test
+ public void backgroundStartPrivilege_equals_works() {
+ assertThat(NONE).isEqualTo(NONE);
+ assertThat(ALLOW_BAL).isEqualTo(ALLOW_BAL);
+ assertThat(ALLOW_FGS).isEqualTo(ALLOW_FGS);
+ assertThat(BSP_ALLOW_A).isEqualTo(BSP_ALLOW_A);
+ assertThat(NONE).isNotEqualTo(ALLOW_BAL);
+ assertThat(ALLOW_FGS).isNotEqualTo(ALLOW_BAL);
+ assertThat(BSP_ALLOW_A).isNotEqualTo(BSP_ALLOW_B);
+ }
}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 9effeec23890..ca6ad6fae46e 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -105,6 +105,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.widget.NotificationProgressModel;
import junit.framework.Assert;
@@ -2414,7 +2415,7 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
- public void progressStyle_getProgressMax_nooSegments_returnsDefault() {
+ public void progressStyle_getProgressMax_noSegments_returnsDefault() {
final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
progressStyle.setProgressSegments(Collections.emptyList());
assertThat(progressStyle.getProgressMax()).isEqualTo(100);
@@ -2459,6 +2460,211 @@ public class NotificationTest {
@Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_onSegmentLimitExceeded_returnsSumOfSegmentLength() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // limit is 10 for ProgressStyle
+ for (int i = 0; i < 30; i++) {
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+ }
+
+ // THEN
+ assertThat(progressStyle.getProgressMax()).isEqualTo(300);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_addProgressSegment_dropsInvalidSegments() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Segments should be a positive integer.
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(0));
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(-1));
+
+ // THEN
+ assertThat(progressStyle.getProgressSegments()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressSegment_dropsInvalidSegments() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Segments should be a positive integer.
+ progressStyle
+ .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(0),
+ new Notification.ProgressStyle.Segment(-1)));
+
+ // THEN
+ assertThat(progressStyle.getProgressSegments()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_addProgressPoint_dropsNegativePoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(-1))
+ .addProgressPoint(new Notification.ProgressStyle.Point(-100));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_setProgressPoint_dropsNegativePoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ // Points should not be a negative integer.
+ progressStyle
+ .setProgressPoints(List.of(new Notification.ProgressStyle.Point(-1),
+ new Notification.ProgressStyle.Point(-100)));
+
+ // THEN
+ assertThat(progressStyle.getProgressPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_ignoresPointsExceedingMax() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+ // Points should not larger than progress maximum.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(101))
+ .addProgressPoint(new Notification.ProgressStyle.Point(500));
+
+ // THEN
+ assertThat(progressStyle.createProgressModel(Color.BLUE, Color.RED).getPoints()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_ignoresOverLimitPoints() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.addProgressSegment(new Notification.ProgressStyle.Segment(100));
+
+ // maximum 4 points are going to be rendered.
+ progressStyle
+ .addProgressPoint(new Notification.ProgressStyle.Point(0))
+ .addProgressPoint(new Notification.ProgressStyle.Point(20))
+ .addProgressPoint(new Notification.ProgressStyle.Point(150))
+ .addProgressPoint(new Notification.ProgressStyle.Point(50))
+ .addProgressPoint(new Notification.ProgressStyle.Point(70))
+ .addProgressPoint(new Notification.ProgressStyle.Point(80))
+ .addProgressPoint(new Notification.ProgressStyle.Point(90))
+ .addProgressPoint(new Notification.ProgressStyle.Point(95))
+ .addProgressPoint(new Notification.ProgressStyle.Point(100));
+ final int backgroundColor = Color.RED;
+ final int defaultProgressColor = Color.BLUE;
+ final int expectedProgressColor = Notification.ProgressStyle.sanitizeProgressColor(
+ /* color = */Notification.COLOR_DEFAULT,
+ /* bg = */backgroundColor,
+ /* defaultColor = */defaultProgressColor);
+
+ // THEN
+ assertThat(progressStyle.createProgressModel(defaultProgressColor, backgroundColor)
+ .getPoints()).isEqualTo(
+ List.of(new Notification.ProgressStyle.Point(0)
+ .setColor(expectedProgressColor),
+ new Notification.ProgressStyle.Point(20)
+ .setColor(expectedProgressColor),
+ new Notification.ProgressStyle.Point(50)
+ .setColor(expectedProgressColor),
+ new Notification.ProgressStyle.Point(70)
+ .setColor(expectedProgressColor)
+ )
+ );
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_mergeSegmentsOnOverflow() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+
+ for (int i = 0; i < 15; i++) {
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+ }
+
+ final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+ Color.BLUE, Color.RED);
+
+ // THEN
+ assertThat(progressModel.getSegments().size()).isEqualTo(1);
+ assertThat(progressModel.getProgressMax()).isEqualTo(150);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_useSegmentColorWhenAllMatch() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ final int segmentColor = Color.YELLOW;
+ final int defaultProgressColor = Color.BLUE;
+ final int backgroundColor = Color.RED;
+ // contrast ensured color for segmentColor.
+ final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor(
+ /* color = */ segmentColor,
+ /* bg = */ backgroundColor,
+ /* defaultColor = */ defaultProgressColor);
+
+ for (int i = 0; i < 15; i++) {
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10)
+ .setColor(segmentColor));
+ }
+
+ final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+ defaultProgressColor, backgroundColor);
+
+ // THEN
+ assertThat(progressModel.getSegments())
+ .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150)
+ .setColor(expectedSegmentColor)));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_createProgressModel_useDefaultColorWhenAllNotMatch() {
+ // GIVEN
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ final int defaultProgressColor = Color.BLUE;
+ final int backgroundColor = Color.RED;
+ // contrast ensured color for default progress color.
+ final int expectedSegmentColor = Notification.ProgressStyle.sanitizeProgressColor(
+ /* color = */ defaultProgressColor,
+ /* bg = */ backgroundColor,
+ /* defaultColor = */ defaultProgressColor);
+
+ for (int i = 0; i < 15; i++) {
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(5)
+ .setColor(Color.BLUE))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(5)
+ .setColor(Color.CYAN));
+ }
+
+ final NotificationProgressModel progressModel = progressStyle.createProgressModel(
+ defaultProgressColor, backgroundColor);
+
+ // THEN
+ assertThat(progressModel.getSegments())
+ .isEqualTo(List.of(new Notification.ProgressStyle.Segment(150)
+ .setColor(expectedSegmentColor)));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void progressStyle_indeterminate_defaultValueFalse() {
final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 911b7ce22741..10a85bcbf488 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -120,7 +120,8 @@ public class ClientTransactionListenerControllerTest {
doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123);
mDisplayManager.registerDisplayListener(mListener, mHandler,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
null /* packageName */);
mController.onDisplayChanged(123);
diff --git a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
index 564460e18294..84bdbe03df13 100644
--- a/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapFactoryTest.java
@@ -16,19 +16,27 @@
package android.graphics;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import android.os.ParcelFileDescriptor;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
-public class BitmapFactoryTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class BitmapFactoryTest {
// tests that we can decode bitmaps from MemoryFiles
@SmallTest
+ @Test
public void testBitmapParcelFileDescriptor() throws Exception {
Bitmap bitmap1 = Bitmap.createBitmap(
new int[] { Color.BLUE }, 1, 1, Bitmap.Config.RGB_565);
diff --git a/core/tests/coretests/src/android/graphics/BitmapTest.java b/core/tests/coretests/src/android/graphics/BitmapTest.java
index 2280cf1cccfa..0126d367eb20 100644
--- a/core/tests/coretests/src/android/graphics/BitmapTest.java
+++ b/core/tests/coretests/src/android/graphics/BitmapTest.java
@@ -16,19 +16,28 @@
package android.graphics;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.hardware.HardwareBuffer;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
-public class BitmapTest extends TestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BitmapTest {
- @SmallTest
+ @Test
public void testBasic() throws Exception {
Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -63,7 +72,7 @@ public class BitmapTest extends TestCase {
assertTrue("getConfig", bm3.getConfig() == Bitmap.Config.ARGB_8888);
}
- @SmallTest
+ @Test
public void testMutability() throws Exception {
Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
Bitmap bm2 = Bitmap.createBitmap(new int[100 * 200], 100, 200,
@@ -82,7 +91,7 @@ public class BitmapTest extends TestCase {
}
}
- @SmallTest
+ @Test
public void testGetPixelsWithAlpha() throws Exception {
int[] colors = new int[100];
for (int i = 0; i < 100; i++) {
@@ -108,7 +117,7 @@ public class BitmapTest extends TestCase {
}
- @SmallTest
+ @Test
public void testGetPixelsWithoutAlpha() throws Exception {
int[] colors = new int[100];
for (int i = 0; i < 100; i++) {
@@ -125,7 +134,7 @@ public class BitmapTest extends TestCase {
}
}
- @SmallTest
+ @Test
public void testSetPixelsWithAlpha() throws Exception {
int[] colors = new int[100];
for (int i = 0; i < 100; i++) {
@@ -151,7 +160,7 @@ public class BitmapTest extends TestCase {
}
}
- @SmallTest
+ @Test
public void testSetPixelsWithoutAlpha() throws Exception {
int[] colors = new int[100];
for (int i = 0; i < 100; i++) {
@@ -181,7 +190,7 @@ public class BitmapTest extends TestCase {
return unpre;
}
- @SmallTest
+ @Test
public void testSetPixelsWithNonOpaqueAlpha() throws Exception {
int[] colors = new int[256];
for (int i = 0; i < 256; i++) {
@@ -238,10 +247,13 @@ public class BitmapTest extends TestCase {
}
}
- @SmallTest
+ private static final int GRAPHICS_USAGE =
+ GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SW_READ_OFTEN
+ | GraphicBuffer.USAGE_SW_WRITE_OFTEN;
+
+ @Test
public void testWrapHardwareBufferWithSrgbColorSpace() {
- GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888,
- GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SOFTWARE_MASK);
+ GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
Canvas canvas = buffer.lockCanvas();
canvas.drawColor(Color.YELLOW);
buffer.unlockCanvasAndPost(canvas);
@@ -252,10 +264,9 @@ public class BitmapTest extends TestCase {
assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), hardwareBitmap.getColorSpace());
}
- @SmallTest
+ @Test
public void testWrapHardwareBufferWithDisplayP3ColorSpace() {
- GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888,
- GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_SOFTWARE_MASK);
+ GraphicBuffer buffer = GraphicBuffer.create(10, 10, PixelFormat.RGBA_8888, GRAPHICS_USAGE);
Canvas canvas = buffer.lockCanvas();
canvas.drawColor(Color.YELLOW);
buffer.unlockCanvasAndPost(canvas);
@@ -267,7 +278,7 @@ public class BitmapTest extends TestCase {
assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), hardwareBitmap.getColorSpace());
}
- @SmallTest
+ @Test
public void testCopyWithDirectByteBuffer() {
// Initialize Bitmap
final int width = 2;
@@ -305,7 +316,7 @@ public class BitmapTest extends TestCase {
assertTrue(bm2.sameAs(bm1));
}
- @SmallTest
+ @Test
public void testCopyWithDirectShortBuffer() {
// Initialize Bitmap
final int width = 2;
@@ -344,7 +355,7 @@ public class BitmapTest extends TestCase {
assertTrue(bm2.sameAs(bm1));
}
- @SmallTest
+ @Test
public void testCopyWithDirectIntBuffer() {
// Initialize Bitmap
final int width = 2;
@@ -383,7 +394,7 @@ public class BitmapTest extends TestCase {
assertTrue(bm2.sameAs(bm1));
}
- @SmallTest
+ @Test
public void testCopyWithHeapByteBuffer() {
// Initialize Bitmap
final int width = 2;
@@ -420,7 +431,7 @@ public class BitmapTest extends TestCase {
assertTrue(bm2.sameAs(bm1));
}
- @SmallTest
+ @Test
public void testCopyWithHeapShortBuffer() {
// Initialize Bitmap
final int width = 2;
@@ -457,7 +468,7 @@ public class BitmapTest extends TestCase {
assertTrue(bm2.sameAs(bm1));
}
- @SmallTest
+ @Test
public void testCopyWithHeapIntBuffer() {
// Initialize Bitmap
final int width = 2;
diff --git a/core/tests/coretests/src/android/graphics/ColorStateListTest.java b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
index ab41bd07ac6d..5cc915e45a6f 100644
--- a/core/tests/coretests/src/android/graphics/ColorStateListTest.java
+++ b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
@@ -16,33 +16,41 @@
package android.graphics;
+import static org.junit.Assert.assertEquals;
+
import android.content.res.ColorStateList;
import android.content.res.Resources;
-import android.test.AndroidTestCase;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.frameworks.coretests.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
- * Tests of {@link android.graphics.ColorStateList}
+ * Tests of {@link ColorStateList}
*/
-public class ColorStateListTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ColorStateListTest {
private Resources mResources;
private int mFailureColor;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mResources = mContext.getResources();
+ @Before
+ public void setUp() throws Exception {
+ mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources();
mFailureColor = mResources.getColor(R.color.failColor);
}
- @SmallTest
+ @Test
public void testStateIsInList() throws Exception {
ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
int[] focusedState = {android.R.attr.state_focused};
@@ -50,7 +58,7 @@ public class ColorStateListTest extends AndroidTestCase {
assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
}
- @SmallTest
+ @Test
public void testStateIsInList_proto() throws Exception {
ColorStateList colorStateList = recreateFromProto(
mResources.getColorStateList(R.color.color1));
@@ -59,7 +67,7 @@ public class ColorStateListTest extends AndroidTestCase {
assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
}
- @SmallTest
+ @Test
public void testEmptyState() throws Exception {
ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
int[] emptyState = {};
@@ -67,7 +75,7 @@ public class ColorStateListTest extends AndroidTestCase {
assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
}
- @SmallTest
+ @Test
public void testEmptyState_proto() throws Exception {
ColorStateList colorStateList = recreateFromProto(
mResources.getColorStateList(R.color.color1));
@@ -76,22 +84,23 @@ public class ColorStateListTest extends AndroidTestCase {
assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
}
- @SmallTest
+ @Test
public void testGetColor() throws Exception {
int defaultColor = mResources.getColor(R.color.color1);
assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
}
- @SmallTest
+ @Test
public void testGetColorWhenListHasNoDefault() throws Exception {
int defaultColor = mResources.getColor(R.color.color_no_default);
assertEquals(mResources.getColor(R.color.testcolor1), defaultColor);
}
- @SmallTest
+ @Test
public void testLstar() throws Exception {
+ var cl = ColorStateList.valueOf(mResources.getColor(R.color.testcolor2)).withLStar(50.0f);
int defaultColor = mResources.getColor(R.color.color_with_lstar);
- assertEquals(mResources.getColor(R.color.testcolor3), defaultColor);
+ assertEquals(cl.getDefaultColor(), defaultColor);
}
private ColorStateList recreateFromProto(ColorStateList colorStateList) throws Exception {
diff --git a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
index 52cc4cac4816..063bdf52fbd2 100644
--- a/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
+++ b/core/tests/coretests/src/android/graphics/FontFileUtilTest.java
@@ -30,9 +30,11 @@ import android.util.Log;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileInputStream;
@@ -43,6 +45,7 @@ import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@SmallTest
+@RunWith(AndroidJUnit4.class)
public class FontFileUtilTest {
private static final String TAG = "FontFileUtilTest";
private static final String CACHE_FILE_PREFIX = ".font";
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
index 8a54e5b998e7..816bde603d36 100644
--- a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -21,10 +21,9 @@ import static com.google.common.truth.Truth.assertThat;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.InstrumentationTestCase;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.text.flags.Flags;
@@ -37,7 +36,7 @@ import org.junit.runner.RunWith;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class PaintFontVariationTest extends InstrumentationTestCase {
+public class PaintFontVariationTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 878ba703c8fe..56760d77e28b 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -18,19 +18,26 @@ package android.graphics;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.InstrumentationTestCase;
import android.text.TextUtils;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.text.flags.Flags;
import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.HashSet;
@@ -38,13 +45,14 @@ import java.util.HashSet;
/**
* PaintTest tests {@link Paint}.
*/
-public class PaintTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class PaintTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
- static void assertEquals(String message, float[] expected, float[] actual) {
+ static void assertFloatArrayEquals(String message, float[] expected, float[] actual) {
if (expected.length != actual.length) {
fail(message + " expected array length:<" + expected.length + "> but was:<"
+ actual.length + ">");
@@ -88,9 +96,10 @@ public class PaintTest extends InstrumentationTestCase {
};
@SmallTest
+ @Test
public void testHintingWidth() {
final Typeface fontTypeface = Typeface.createFromAsset(
- getInstrumentation().getContext().getAssets(), FONT_PATH);
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets(), FONT_PATH);
Paint paint = new Paint();
paint.setTypeface(fontTypeface);
@@ -103,12 +112,14 @@ public class PaintTest extends InstrumentationTestCase {
paint.setHinting(Paint.HINTING_OFF);
paint.getTextWidths(String.valueOf(testCase.mText), widths);
- assertEquals("Text width of '" + testCase.mText + "' without hinting is not expected.",
+ assertFloatArrayEquals(
+ "Text width of '" + testCase.mText + "' without hinting is not expected.",
testCase.mWidthWithoutHinting, widths);
paint.setHinting(Paint.HINTING_ON);
paint.getTextWidths(String.valueOf(testCase.mText), widths);
- assertEquals("Text width of '" + testCase.mText + "' with hinting is not expected.",
+ assertFloatArrayEquals(
+ "Text width of '" + testCase.mText + "' with hinting is not expected.",
testCase.mWidthWithHinting, widths);
}
}
@@ -131,9 +142,11 @@ public class PaintTest extends InstrumentationTestCase {
return sb.toString();
}
+ @Test
public void testHasGlyph_variationSelectors() {
final Typeface fontTypeface = Typeface.createFromAsset(
- getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf");
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets(),
+ "fonts/hasGlyphTestFont.ttf");
Paint p = new Paint();
p.setTypeface(fontTypeface);
@@ -175,6 +188,7 @@ public class PaintTest extends InstrumentationTestCase {
}
}
+ @Test
public void testGetTextRunAdvances() {
{
// LTR
@@ -231,6 +245,7 @@ public class PaintTest extends InstrumentationTestCase {
}
}
+ @Test
public void testGetTextRunAdvances_invalid() {
Paint p = new Paint();
char[] text = "test".toCharArray();
@@ -284,6 +299,7 @@ public class PaintTest extends InstrumentationTestCase {
}
}
+ @Test
public void testMeasureTextBidi() {
Paint p = new Paint();
{
@@ -340,18 +356,21 @@ public class PaintTest extends InstrumentationTestCase {
}
}
+ @Test
public void testSetGetWordSpacing() {
Paint p = new Paint();
- assertEquals(0.0f, p.getWordSpacing()); // The default value should be 0.
+ assertEquals(0.0f, p.getWordSpacing(), 0.0f); // The default value should be 0.
p.setWordSpacing(1.0f);
- assertEquals(1.0f, p.getWordSpacing());
+ assertEquals(1.0f, p.getWordSpacing(), 0.0f);
p.setWordSpacing(-2.0f);
- assertEquals(-2.0f, p.getWordSpacing());
+ assertEquals(-2.0f, p.getWordSpacing(), 0.0f);
}
+ @Test
public void testGetUnderlinePositionAndThickness() {
final Typeface fontTypeface = Typeface.createFromAsset(
- getInstrumentation().getContext().getAssets(), "fonts/underlineTestFont.ttf");
+ InstrumentationRegistry.getInstrumentation().getContext().getAssets(),
+ "fonts/underlineTestFont.ttf");
final Paint p = new Paint();
final int textSize = 100;
p.setTextSize(textSize);
@@ -391,6 +410,7 @@ public class PaintTest extends InstrumentationTestCase {
return ccByChars;
}
+ @Test
public void testCluster() {
final Paint p = new Paint();
p.setTextSize(100);
@@ -417,6 +437,7 @@ public class PaintTest extends InstrumentationTestCase {
}
@RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
public void testDerivedFromSameTypeface() {
final Paint p = new Paint();
@@ -432,6 +453,7 @@ public class PaintTest extends InstrumentationTestCase {
}
@RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
public void testDerivedFromChained() {
final Paint p = new Paint();
diff --git a/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java b/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
index e1ca7dfb7cc2..fbaf502596f7 100644
--- a/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
+++ b/core/tests/coretests/src/android/graphics/ThreadBitmapTest.java
@@ -16,17 +16,17 @@
package android.graphics;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
-public class ThreadBitmapTest extends TestCase {
-
- @Override
- protected void setUp() throws Exception {
- }
+@RunWith(AndroidJUnit4.class)
+public class ThreadBitmapTest {
@LargeTest
+ @Test
public void testCreation() {
for (int i = 0; i < 200; i++) {
@@ -44,4 +44,3 @@ public class ThreadBitmapTest extends TestCase {
public void run() {}
}
}
-
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 14292725506e..2b6eda8f0988 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -39,6 +39,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.text.flags.Flags;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -514,9 +516,14 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
paint.setElegantTextHeight(false);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
- assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ if (Flags.deprecateElegantTextHeightApi()) {
+ // Calling setElegantTextHeight is no-op.
+ assertTrue(paint.isElegantTextHeight());
+ } else {
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
}
@Test
@@ -553,9 +560,14 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
paint.setElegantTextHeight(false);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
- assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+ if (Flags.deprecateElegantTextHeightApi()) {
+ // Calling setElegantTextHeight is no-op.
+ assertTrue(paint.isElegantTextHeight());
+ } else {
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
testTypeface = fontMap.get("sans-serif");
assertNotNull(testTypeface);
@@ -566,9 +578,14 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
paint.setElegantTextHeight(false);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
- assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
- assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+ if (Flags.deprecateElegantTextHeightApi()) {
+ // Calling setElegantTextHeight is no-op.
+ assertTrue(paint.isElegantTextHeight());
+ } else {
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0.0f);
+ assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0.0f);
+ assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 0.0f);
+ }
}
@Test
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 7a5b3064b3a3..a270848b98a3 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -73,9 +73,13 @@ public class DisplayManagerGlobalTest {
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ private static final long DISPLAY_CHANGE_EVENTS =
+ DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+
private static final long ALL_DISPLAY_EVENTS =
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DISPLAY_CHANGE_EVENTS
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
@Mock
@@ -127,7 +131,7 @@ public class DisplayManagerGlobalTest {
final DisplayInfo newDisplayInfo = new DisplayInfo();
newDisplayInfo.rotation++;
doReturn(newDisplayInfo).when(mDisplayManager).getDisplayInfo(displayId);
- callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
Mockito.verify(mDisplayListener).onDisplayChanged(eq(displayId));
Mockito.verifyNoMoreInteractions(mDisplayListener);
@@ -186,8 +190,8 @@ public class DisplayManagerGlobalTest {
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener, mHandler,
ALL_DISPLAY_EVENTS
- & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
- callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ & ~DISPLAY_CHANGE_EVENTS, null);
+ callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
waitForHandler();
Mockito.verifyZeroInteractions(mDisplayListener);
@@ -257,8 +261,7 @@ public class DisplayManagerGlobalTest {
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
null /* packageName */);
mDisplayManagerGlobal.registerDisplayListener(mDisplayListener2, mHandler,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
- null /* packageName */);
+ DISPLAY_CHANGE_EVENTS, null /* packageName */);
mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
waitForHandler();
@@ -304,8 +307,7 @@ public class DisplayManagerGlobalTest {
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_ADDED, 0));
- assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
- mDisplayManagerGlobal
+ assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
.mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
index 4a227d8ff1ef..255d09b854bd 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -86,7 +86,7 @@ class DisplayTopologyTest {
verifyDisplay(display1, displayId1, width1, height1, noOfChildren = 1)
val display2 = display1.children[0]
- verifyDisplay(display1.children[0], displayId2, width2, height2, POSITION_TOP,
+ verifyDisplay(display2, displayId2, width2, height2, POSITION_TOP,
offset = width1 / 2 - width2 / 2, noOfChildren = 1)
var display = display2
@@ -99,6 +99,76 @@ class DisplayTopologyTest {
}
@Test
+ fun updateDisplay() {
+ val displayId = 1
+ val width = 800f
+ val height = 600f
+
+ val newWidth = 1000f
+ val newHeight = 500f
+ topology.addDisplay(displayId, width, height)
+ assertThat(topology.updateDisplay(displayId, newWidth, newHeight)).isTrue()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+ verifyDisplay(topology.root!!, displayId, newWidth, newHeight, noOfChildren = 0)
+ }
+
+ @Test
+ fun updateDisplay_notUpdated() {
+ val displayId = 1
+ val width = 800f
+ val height = 600f
+ topology.addDisplay(displayId, width, height)
+
+ // Same size
+ assertThat(topology.updateDisplay(displayId, width, height)).isFalse()
+
+ // Display doesn't exist
+ assertThat(topology.updateDisplay(/* displayId= */ 100, width, height)).isFalse()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+ verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
+ }
+
+ @Test
+ fun updateDisplayDoesNotAffectDefaultTopology() {
+ val width1 = 700f
+ val height = 600f
+ topology.addDisplay(/* displayId= */ 1, width1, height)
+
+ val width2 = 800f
+ val noOfDisplays = 30
+ for (i in 2..noOfDisplays) {
+ topology.addDisplay(/* displayId= */ i, width2, height)
+ }
+
+ val displaysToUpdate = arrayOf(3, 7, 18)
+ val newWidth = 1000f
+ val newHeight = 1500f
+ for (i in displaysToUpdate) {
+ assertThat(topology.updateDisplay(/* displayId= */ i, newWidth, newHeight)).isTrue()
+ }
+
+ assertThat(topology.primaryDisplayId).isEqualTo(1)
+
+ val display1 = topology.root!!
+ verifyDisplay(display1, id = 1, width1, height, noOfChildren = 1)
+
+ val display2 = display1.children[0]
+ verifyDisplay(display2, id = 2, width2, height, POSITION_TOP,
+ offset = width1 / 2 - width2 / 2, noOfChildren = 1)
+
+ var display = display2
+ for (i in 3..noOfDisplays) {
+ display = display.children[0]
+ // The last display should have no children
+ verifyDisplay(display, id = i, if (i in displaysToUpdate) newWidth else width2,
+ if (i in displaysToUpdate) newHeight else height, POSITION_RIGHT, offset = 0f,
+ noOfChildren = if (i < noOfDisplays) 1 else 0)
+ }
+ }
+
+ @Test
fun removeDisplays() {
val displayId1 = 1
val width1 = 800f
@@ -117,7 +187,7 @@ class DisplayTopologyTest {
}
var removedDisplays = arrayOf(20)
- topology.removeDisplay(20)
+ assertThat(topology.removeDisplay(20)).isTrue()
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
@@ -139,11 +209,11 @@ class DisplayTopologyTest {
noOfChildren = if (i < noOfDisplays) 1 else 0)
}
- topology.removeDisplay(22)
+ assertThat(topology.removeDisplay(22)).isTrue()
removedDisplays += 22
- topology.removeDisplay(23)
+ assertThat(topology.removeDisplay(23)).isTrue()
removedDisplays += 23
- topology.removeDisplay(25)
+ assertThat(topology.removeDisplay(25)).isTrue()
removedDisplays += 25
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
@@ -174,7 +244,7 @@ class DisplayTopologyTest {
val height = 600f
topology.addDisplay(displayId, width, height)
- topology.removeDisplay(displayId)
+ assertThat(topology.removeDisplay(displayId)).isTrue()
assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
assertThat(topology.root).isNull()
@@ -187,7 +257,7 @@ class DisplayTopologyTest {
val height = 600f
topology.addDisplay(displayId, width, height)
- topology.removeDisplay(3)
+ assertThat(topology.removeDisplay(3)).isFalse()
assertThat(topology.primaryDisplayId).isEqualTo(displayId)
verifyDisplay(topology.root!!, displayId, width, height, noOfChildren = 0)
@@ -203,7 +273,7 @@ class DisplayTopologyTest {
topology = DisplayTopology(/* root= */ null, displayId2)
topology.addDisplay(displayId1, width, height)
topology.addDisplay(displayId2, width, height)
- topology.removeDisplay(displayId2)
+ assertThat(topology.removeDisplay(displayId2)).isTrue()
assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
verifyDisplay(topology.root!!, displayId1, width, height, noOfChildren = 0)
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index fb1efa86c236..8b4f714fbf65 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -1171,6 +1171,88 @@ public class ViewFrameRateTest {
waitForAfterDraw();
}
+ @Test
+ public void ignoreHeuristicWhenFling() throws Throwable {
+ if (!ViewProperties.vrr_enabled().orElse(true)) {
+ return;
+ }
+
+ waitForFrameRateCategoryToSettle();
+ FrameLayout host = new FrameLayout(mActivity);
+ View childView = new View(mActivity);
+ float velocity = 1000;
+
+ TranslateAnimation translateAnimation = new TranslateAnimation(
+ Animation.RELATIVE_TO_PARENT, 0f, // fromXDelta
+ Animation.RELATIVE_TO_PARENT, 0f, // toXDelta
+ Animation.RELATIVE_TO_PARENT, 1f, // fromYDelta (100%p)
+ Animation.RELATIVE_TO_PARENT, 0f // toYDelta
+ );
+ translateAnimation.setDuration(100);
+
+ mActivityRule.runOnUiThread(() -> {
+ ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mActivity.setContentView(host, fullSize);
+ host.setFrameContentVelocity(velocity);
+ ViewGroupOverlay overlay = host.getOverlay();
+ overlay.add(childView);
+ assertEquals(velocity, host.getFrameContentVelocity());
+ assertEquals(host.getFrameContentVelocity(),
+ ((View) childView.getParent()).getFrameContentVelocity());
+
+ mMovingView.startAnimation(translateAnimation);
+
+ // The frame rate should be "Normal" during fling gestures,
+ // even if there's a moving View.
+ assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ });
+ waitForAfterDraw();
+ }
+
+ @Test
+ public void ignoreHeuristicWhenFlingMovementFirst() throws Throwable {
+ if (!ViewProperties.vrr_enabled().orElse(true)) {
+ return;
+ }
+
+ waitForFrameRateCategoryToSettle();
+ FrameLayout host = new FrameLayout(mActivity);
+ View childView = new View(mActivity);
+ float velocity = 1000;
+
+ TranslateAnimation translateAnimation = new TranslateAnimation(
+ Animation.RELATIVE_TO_PARENT, 0f, // fromXDelta
+ Animation.RELATIVE_TO_PARENT, 0f, // toXDelta
+ Animation.RELATIVE_TO_PARENT, 1f, // fromYDelta (100%p)
+ Animation.RELATIVE_TO_PARENT, 0f // toYDelta
+ );
+ translateAnimation.setDuration(100);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.startAnimation(translateAnimation);
+
+ ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mActivity.setContentView(host, fullSize);
+ host.setFrameContentVelocity(velocity);
+ ViewGroupOverlay overlay = host.getOverlay();
+ overlay.add(childView);
+ assertEquals(velocity, host.getFrameContentVelocity());
+ assertEquals(host.getFrameContentVelocity(),
+ ((View) childView.getParent()).getFrameContentVelocity());
+
+ // The frame rate should be "Normal" during fling gestures,
+ // even if there's a moving View.
+ assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ });
+ waitForAfterDraw();
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index ed9fc1c9e547..18ab52dba8f3 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -25,7 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
-import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -861,10 +861,10 @@ public class ViewRootImplTest {
assertEquals(mViewRootImpl.getFrameRateCompatibility(),
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
assertFalse(mViewRootImpl.isFrameRateConflicted());
- mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
+ mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_AT_LEAST);
if (toolkitFrameRateVelocityMappingReadOnly()) {
assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1);
- assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST,
mViewRootImpl.getFrameRateCompatibility());
assertFalse(mViewRootImpl.isFrameRateConflicted());
} else {
@@ -888,10 +888,10 @@ public class ViewRootImplTest {
sInstrumentation.runOnMainSync(() -> {
assertFalse(mViewRootImpl.isFrameRateConflicted());
- mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+ mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST);
if (toolkitFrameRateVelocityMappingReadOnly()) {
assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1);
- assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ assertEquals(FRAME_RATE_COMPATIBILITY_AT_LEAST,
mViewRootImpl.getFrameRateCompatibility());
} else {
assertEquals(FRAME_RATE_CATEGORY_HIGH,
@@ -904,7 +904,7 @@ public class ViewRootImplTest {
mViewRootImpl.getFrameRateCompatibility());
// Should be false since 60 is a divisor of 120.
assertFalse(mViewRootImpl.isFrameRateConflicted());
- mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
+ mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_AT_LEAST);
assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
// compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
// since the frame rate 60 is smaller than 120.
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 5a4561d7c6ea..f87b6994900f 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -266,6 +266,11 @@ public class ContentCaptureSessionTest {
}
@Override
+ void internalNotifySessionFlushEvent(int sessionId) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
void internalNotifyChildSessionStarted(int parentSessionId, int childSessionId,
@NonNull ContentCaptureContext clientContext) {
throw new UnsupportedOperationException("should not have been called");
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 00b4f464de50..d1fbc77cbd46 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doNothing;
@@ -44,20 +45,27 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManagerGlobal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
+import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.widget.flags.Flags;
import com.google.android.collect.Lists;
@@ -76,6 +84,8 @@ import java.util.concurrent.CompletableFuture;
public class LockPatternUtilsTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private ILockSettings mLockSettings;
private static final int USER_ID = 1;
@@ -395,4 +405,156 @@ public class LockPatternUtilsTest {
}
};
}
+
+ private InputManagerGlobal.TestSession configureExternalHardwareTest(InputDevice[] devices)
+ throws RemoteException {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ final ILockSettings ils = mock(ILockSettings.class);
+ when(ils.getBoolean(anyString(), anyBoolean(), anyInt())).thenThrow(RemoteException.class);
+ mLockPatternUtils = new LockPatternUtils(context, ils);
+
+ IInputManager inputManagerMock = mock(IInputManager.class);
+
+ int[] deviceIds = new int[devices.length];
+
+ for (int i = 0; i < devices.length; i++) {
+ when(inputManagerMock.getInputDevice(i)).thenReturn(devices[i]);
+ }
+
+ when(inputManagerMock.getInputDeviceIds()).thenReturn(deviceIds);
+ InputManagerGlobal.TestSession session =
+ InputManagerGlobal.createTestSession(inputManagerMock);
+
+ return session;
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_noDevicesAttached() throws RemoteException {
+ InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
+ assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_noEnabledDeviceAttached() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder.setEnabled(false);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_withoutHwKeyboard() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder.setEnabled(true).setSources(InputDevice.SOURCE_TOUCHSCREEN);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_withoutFullHwKeyboard() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_withHwKeyboardOldDefault() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertFalse(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isPinEnhancedPrivacyEnabled_withHwKeyboard() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertTrue(mLockPatternUtils.isPinEnhancedPrivacyEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isVisiblePatternEnabled_noDevices() throws RemoteException {
+ InputManagerGlobal.TestSession session = configureExternalHardwareTest(new InputDevice[0]);
+ assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isVisiblePatternEnabled_noEnabledDevices() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder.setEnabled(false);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isVisiblePatternEnabled_noPointingDevices() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_TOUCHSCREEN);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isVisiblePatternEnabled_externalPointingDevice() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_CLASS_POINTER);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertFalse(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+ session.close();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT)
+ public void isVisiblePatternEnabled_externalPointingDeviceOldDefault() throws RemoteException {
+ InputDevice.Builder builder = new InputDevice.Builder();
+ builder
+ .setEnabled(true)
+ .setSources(InputDevice.SOURCE_CLASS_POINTER);
+ InputManagerGlobal.TestSession session =
+ configureExternalHardwareTest(new InputDevice[]{builder.build()});
+ assertTrue(mLockPatternUtils.isVisiblePatternEnabled(USER_ID));
+ session.close();
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2398e7134b34..f136e065a405 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -125,6 +125,7 @@ applications that come with the platform
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+ <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
</privapp-permissions>
<privapp-permissions package="com.android.phone">
@@ -609,6 +610,8 @@ applications that come with the platform
<permission name="android.permission.MANAGE_INTRUSION_DETECTION_STATE" />
<!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+ <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
+ <permission name="android.permission.RESOLVE_COMPONENT_FOR_UID"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 5f1fb4b44613..500548500927 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -171,3 +171,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "task_view_repository"
+ namespace: "multitasking"
+ description: "Factor task-view state tracking out of taskviewtransitions"
+ bug: "384976265"
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp
index 41d1b5c15369..eecf199a3ec2 100644
--- a/libs/WindowManager/Shell/multivalentTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentTests/Android.bp
@@ -55,6 +55,7 @@ android_robolectric_test {
"truth",
"flag-junit-base",
"flag-junit",
+ "testables",
],
auto_gen_config: true,
}
@@ -77,6 +78,7 @@ android_test {
"truth",
"platform-test-annotations",
"platform-test-rules",
+ "testables",
],
libs: [
"android.test.base.stubs.system",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index 0d8f80935f5a..3e01256fd67c 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles.bar
+import android.animation.AnimatorTestRule
+import android.app.ActivityManager
import android.content.Context
import android.graphics.Insets
import android.graphics.Rect
@@ -23,7 +25,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -36,27 +37,34 @@ import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubbleOverflow
import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
-import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
/** Tests for [BubbleBarAnimationHelper] */
@SmallTest
@RunWith(AndroidJUnit4::class)
class BubbleBarAnimationHelperTest {
- companion object {
- @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+ @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
+ companion object {
const val SCREEN_WIDTH = 2000
const val SCREEN_HEIGHT = 1000
}
@@ -148,6 +156,26 @@ class BubbleBarAnimationHelperTest {
}
@Test
+ fun animateSwitch_bubbleToBubble_updateTaskBounds() {
+ val fromBubble = createBubble("from").initialize(container)
+ val toBubbleTaskController = mock<TaskViewTaskController>()
+ val toBubble = createBubble("to", toBubbleTaskController).initialize(container)
+
+ getInstrumentation().runOnMainSync {
+ animationHelper.animateSwitch(fromBubble, toBubble) {}
+ // Start the animation, but don't finish
+ animatorTestRule.advanceTimeBy(100)
+ }
+ getInstrumentation().waitForIdleSync()
+ // Clear invocations to ensure that bounds update happens after animation ends
+ clearInvocations(toBubbleTaskController)
+ getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
+ getInstrumentation().waitForIdleSync()
+
+ verify(toBubbleTaskController).setWindowBounds(any())
+ }
+
+ @Test
fun animateSwitch_bubbleToOverflow_oldHiddenNewShown() {
val fromBubble = createBubble(key = "from").initialize(container)
val overflow = createOverflow().initialize(container)
@@ -193,13 +221,43 @@ class BubbleBarAnimationHelperTest {
assertThat(toBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse()
}
- private fun createBubble(key: String): Bubble {
+ @Test
+ fun animateToRestPosition_updateTaskBounds() {
+ val taskController = mock<TaskViewTaskController>()
+ val bubble = createBubble("key", taskController).initialize(container)
+
+ getInstrumentation().runOnMainSync {
+ animationHelper.animateExpansion(bubble) {}
+ animatorTestRule.advanceTimeBy(1000)
+ }
+ getInstrumentation().waitForIdleSync()
+ getInstrumentation().runOnMainSync {
+ animationHelper.animateToRestPosition()
+ animatorTestRule.advanceTimeBy(100)
+ }
+ // Clear invocations to ensure that bounds update happens after animation ends
+ clearInvocations(taskController)
+ getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(900) }
+ getInstrumentation().waitForIdleSync()
+
+ verify(taskController).setWindowBounds(any())
+ }
+
+ private fun createBubble(
+ key: String,
+ taskViewTaskController: TaskViewTaskController = mock<TaskViewTaskController>(),
+ ): Bubble {
+ val taskView = TaskView(context, taskViewTaskController)
+ val taskInfo = mock<ActivityManager.RunningTaskInfo>()
+ whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo)
+ val bubbleTaskView = BubbleTaskView(taskView, mainExecutor)
+
val bubbleBarExpandedView =
FakeBubbleFactory.createExpandedView(
context,
bubblePositioner,
expandedViewManager,
- FakeBubbleTaskViewFactory(context, mainExecutor).create(),
+ bubbleTaskView,
mainExecutor,
bgExecutor,
bubbleLogger,
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 3e8a9b64dac6..3188e5b9c6d2 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
@@ -463,6 +463,7 @@ public class BubbleBarAnimationHelper {
super.onAnimationEnd(animation);
bbev.resetPivot();
bbev.setDragging(false);
+ updateExpandedView(bbev);
}
});
startNewAnimator(animatorSet);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index c74bf53268f9..9ebb7f5aa270 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -643,7 +643,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
t.setPosition(animatingLeash, x, endY);
t.setAlpha(animatingLeash, 1.f);
}
- dispatchEndPositioning(mDisplayId, mCancelled, t);
+ if (!android.view.inputmethod.Flags.refactorInsetsController()) {
+ dispatchEndPositioning(mDisplayId, mCancelled, t);
+ }
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
@@ -659,6 +661,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // In split screen, we also set {@link
+ // WindowContainer#mExcludeInsetsTypes} but this should only happen after
+ // the IME client visibility was set. Otherwise the insets will we
+ // dispatched too early, and we get a flicker. Thus, only dispatching it
+ // after reporting that the IME is hidden to system server.
+ dispatchEndPositioning(mDisplayId, mCancelled, t);
+ }
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 21c44c9b92ee..4bcec702281d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -571,9 +571,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// For flexible split, expand app offscreen as well
if (mDividerSnapAlgorithm.areOffscreenRatiosSupported()) {
if (position <= mDividerSnapAlgorithm.getMiddleTarget().position) {
- bounds1.top = bounds1.bottom - bounds2.width();
+ bounds1.top = bounds1.bottom - bounds2.height();
} else {
- bounds2.bottom = bounds2.top + bounds1.width();
+ bounds2.bottom = bounds2.top + bounds1.height();
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index dfa2d9b6bb63..9a60cfeed7c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -38,6 +38,7 @@ import androidx.core.util.putAll
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
@@ -236,6 +237,7 @@ class DesktopModeLoggerTransitionObserver(
) {
// Sessions is finishing, log task updates followed by an exit event
identifyAndLogTaskUpdates(
+ transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
)
@@ -252,12 +254,14 @@ class DesktopModeLoggerTransitionObserver(
desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo))
identifyAndLogTaskUpdates(
+ transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
)
} else if (isSessionActive) {
// Session is neither starting, nor finishing, log task updates if there are any
identifyAndLogTaskUpdates(
+ transitionInfo,
preTransitionVisibleFreeformTasks,
postTransitionVisibleFreeformTasks,
)
@@ -270,6 +274,7 @@ class DesktopModeLoggerTransitionObserver(
/** Compare the old and new state of taskInfos and identify and log the changes */
private fun identifyAndLogTaskUpdates(
+ transitionInfo: TransitionInfo,
preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
) {
@@ -304,9 +309,19 @@ class DesktopModeLoggerTransitionObserver(
// find old tasks that were removed
preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
- desktopModeEventLogger.logTaskRemoved(
- buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())
- )
+ val minimizeReason =
+ if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) {
+ MinimizeReason.MINIMIZE_BUTTON
+ } else {
+ null
+ }
+ val taskUpdate =
+ buildTaskUpdateForTask(
+ taskInfo,
+ postTransitionVisibleFreeformTasks.size(),
+ minimizeReason,
+ )
+ desktopModeEventLogger.logTaskRemoved(taskUpdate)
Trace.setCounter(
Trace.TRACE_TAG_WINDOW_MANAGER,
VISIBLE_TASKS_COUNTER_NAME,
@@ -320,7 +335,11 @@ class DesktopModeLoggerTransitionObserver(
}
}
- private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate {
+ private fun buildTaskUpdateForTask(
+ taskInfo: TaskInfo,
+ visibleTasks: Int,
+ minimizeReason: MinimizeReason? = null,
+ ): TaskUpdate {
val screenBounds = taskInfo.configuration.windowConfiguration.bounds
val positionInParent = taskInfo.positionInParent
return TaskUpdate(
@@ -331,6 +350,7 @@ class DesktopModeLoggerTransitionObserver(
taskX = positionInParent.x,
taskY = positionInParent.y,
visibleTaskCount = visibleTasks,
+ minimizeReason = minimizeReason,
)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 606a729305b4..90191345147c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -82,7 +82,7 @@ fun calculateInitialBounds(
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
Size(
- taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
+ taskInfo.appCompatTaskInfo.topActivityAppBounds.width(),
idealSize.height,
)
} else {
@@ -104,7 +104,7 @@ fun calculateInitialBounds(
// apply custom app width.
Size(
customPortraitWidthForLandscapeApp,
- taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight,
+ taskInfo.appCompatTaskInfo.topActivityAppBounds.height(),
)
} else {
// For portrait resizeable activities, simply apply ideal size.
@@ -196,13 +196,8 @@ fun maximizeSizeGivenAspectRatio(
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
- val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
- val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
- if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed || !taskInfo.canChangeAspectRatio) {
- return maxOf(appLetterboxWidth, appLetterboxHeight) /
- minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
- }
- val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f
+ if (taskInfo.appCompatTaskInfo.topActivityAppBounds.isEmpty) return 1f
+ val appBounds = taskInfo.appCompatTaskInfo.topActivityAppBounds
return maxOf(appBounds.height(), appBounds.width()) /
minOf(appBounds.height(), appBounds.width()).toFloat()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 45faba6e341f..0330a5f0c4e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -127,14 +127,20 @@ class DesktopTasksLimiter(
override fun onTransitionStarting(transition: IBinder) {
val mActiveTaskDetails = activeTransitionTokensAndTasks[transition]
- if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) {
- // Begin minimize window CUJ instrumentation.
- interactionJankMonitor.begin(
- mActiveTaskDetails.transitionInfo?.rootLeash,
- context,
- handler,
- CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
- )
+ val info = mActiveTaskDetails?.transitionInfo ?: return
+ val minimizeChange = getMinimizeChange(info, mActiveTaskDetails.taskId) ?: return
+ // Begin minimize window CUJ instrumentation.
+ interactionJankMonitor.begin(
+ minimizeChange.leash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
+ )
+ }
+
+ private fun getMinimizeChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
+ return info.changes.find { change ->
+ change.taskInfo?.taskId == taskId && change.mode == TRANSIT_TO_BACK
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index a611fe1db2ce..c4ff87d175a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -74,7 +74,7 @@ public class DragSession {
mInitialDragData = data;
mInitialDragFlags = dragFlags;
displayLayout = dispLayout;
- hideDragSourceTaskId = data.getDescription().getExtras() != null
+ hideDragSourceTaskId = data != null && data.getDescription().getExtras() != null
? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1)
: -1;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 7928e5ed4188..a7a5f09c88f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -935,10 +935,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// back to the decoration using
// {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
// should shared with the maximize menu's maximize/restore actions.
+ final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
+ decoration.mTaskInfo.userId);
if (Flags.enableFullyImmersiveInDesktop()
- && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) {
- // Task is requesting immersive, so it should either enter or exit immersive,
- // depending on immersive state.
+ && desktopRepository.isTaskInFullImmersiveState(
+ decoration.mTaskInfo.taskId)) {
+ // Task is in immersive and should exit.
onEnterOrExitImmersive(decoration.mTaskInfo);
} else {
// Full immersive is disabled or task doesn't request/support it, so just
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 9972247df46d..ab1ac1a0efa3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -44,6 +44,7 @@ import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition
import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
+import android.tools.flicker.assertors.assertions.VisibleLayersShownMoreThanOneConsecutiveEntry
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
@@ -463,7 +464,9 @@ class DesktopModeFlickerScenarios {
}
),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also {
+ it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry())
+ } +
listOf(
AppWindowOnTopAtStart(DESKTOP_MODE_APP),
AppWindowBecomesInvisible(DESKTOP_MODE_APP),
@@ -489,7 +492,9 @@ class DesktopModeFlickerScenarios {
}
),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS.toMutableMap().also {
+ it.remove(VisibleLayersShownMoreThanOneConsecutiveEntry())
+ } +
listOf(
AppWindowOnTopAtStart(DESKTOP_MODE_APP),
AppWindowBecomesInvisible(DESKTOP_MODE_APP),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt
new file mode 100644
index 000000000000..56f1dcbf838c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsWithKeyboard.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing META + -.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsWithKeyboard : MinimizeAppWindows(usingKeyboard = true) {
+ @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+ @Test
+ override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(MINIMIZE_APP)
+ .use(MINIMIZE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
index 971637b62604..835559cd0936 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -43,7 +43,10 @@ import org.junit.Test
*/
@Ignore("Test Base Class")
abstract class MinimizeAppWindows
-constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+constructor(
+ private val rotation: Rotation = Rotation.ROTATION_0,
+ private val usingKeyboard: Boolean = false
+) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
private val wmHelper = WindowManagerStateHelper(instrumentation)
@@ -68,9 +71,9 @@ constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun minimizeAllAppWindows() {
- testApp3.minimizeDesktopApp(wmHelper, device)
- testApp2.minimizeDesktopApp(wmHelper, device)
- testApp1.minimizeDesktopApp(wmHelper, device)
+ testApp3.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+ testApp2.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+ testApp1.minimizeDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index abd707817621..c0ff2f0652b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -45,6 +45,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_M
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -77,6 +78,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height()
}
+ @After
+ fun tearDown() {
+ clearInvocations(staticMockMarker(FrameworkStatsLog::class.java))
+ clearInvocations(staticMockMarker(EventLogTags::class.java))
+ }
+
@Test
fun logSessionEnter_logsEnterReasonWithNewSessionId() {
desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 43684fb92b64..0154ff31c989 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
@@ -566,7 +567,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
assertFalse(transitionObserver.isSessionActive)
verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE))
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON))
+ )
verifyZeroInteractions(desktopModeEventLogger)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 4f37180baa37..e1c2153014fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -4160,8 +4160,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
configuration.windowConfiguration.appBounds = bounds
}
- appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
- appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height())
isResizeable = false
}
@@ -4879,15 +4878,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
if (deviceOrientation == ORIENTATION_LANDSCAPE) {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
- appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
- appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
+ appCompatTaskInfo.topActivityAppBounds.set(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_LONG,
+ DISPLAY_DIMENSION_SHORT,
+ )
} else {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
- appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
- appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
+ appCompatTaskInfo.topActivityAppBounds.set(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_SHORT,
+ DISPLAY_DIMENSION_LONG,
+ )
}
if (shouldLetterbox) {
@@ -4897,17 +4900,15 @@ class DesktopTasksControllerTest : ShellTestCase() {
screenOrientation == SCREEN_ORIENTATION_PORTRAIT
) {
// Letterbox to portrait size
- appCompatTaskInfo.setTopActivityLetterboxed(true)
- appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
- appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
+ appCompatTaskInfo.isTopActivityLetterboxed = true
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600)
} else if (
deviceOrientation == ORIENTATION_PORTRAIT &&
screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
) {
// Letterbox to landscape size
- appCompatTaskInfo.setTopActivityLetterboxed(true)
- appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
- appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
+ appCompatTaskInfo.isTopActivityLetterboxed = true
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index e6f1fcf7f14f..52602f22fd4b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -551,7 +551,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
.getTransitionObserver()
.onTransitionReady(
transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
StubTransaction() /* startTransaction */,
StubTransaction(), /* finishTransaction */
)
@@ -583,7 +583,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
.getTransitionObserver()
.onTransitionReady(
transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
StubTransaction() /* startTransaction */,
StubTransaction(), /* finishTransaction */
)
@@ -616,7 +616,7 @@ class DesktopTasksLimiterTest : ShellTestCase() {
.getTransitionObserver()
.onTransitionReady(
mergedTransition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
StubTransaction() /* startTransaction */,
StubTransaction(), /* finishTransaction */
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
index 3d59342f62d8..8ccca07142aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragSessionTest.kt
@@ -59,6 +59,13 @@ class DragSessionTest : ShellTestCase() {
}
@Test
+ fun testNullClipData() {
+ // Start a new drag session with null data
+ val session = DragSession(activityTaskManager, displayLayout, null, 0)
+ assertThat(session.hideDragSourceTaskId).isEqualTo(-1)
+ }
+
+ @Test
fun testGetRunningTask() {
// Set up running tasks
val runningTasks = listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index aead0a7afb53..ffe8e7135513 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -1054,26 +1054,6 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun testImmersiveButtonClick_entersImmersiveMode() {
- val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
- as ArgumentCaptor<View.OnClickListener>
- val decor = createOpenTaskDecoration(
- windowingMode = WINDOWING_MODE_FREEFORM,
- onCaptionButtonClickListener = onClickListenerCaptor,
- requestingImmersive = true,
- )
- val view = mock(View::class.java)
- whenever(view.id).thenReturn(R.id.maximize_window)
- whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
- .thenReturn(false)
-
- onClickListenerCaptor.value.onClick(view)
-
- verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun testImmersiveRestoreButtonClick_exitsImmersiveMode() {
val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
as ArgumentCaptor<View.OnClickListener>
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 1bc15d72bacc..cc4a29b31996 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -199,6 +199,7 @@ cc_test {
// This is to suppress warnings/errors from gtest
"-Wno-unnamed-type-template-args",
],
+ require_root: true,
srcs: [
// Helpers/infra for testing.
"tests/CommonHelpers.cpp",
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e6182454ad8a..5955915c9fcd 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -1420,18 +1420,20 @@ void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo
Mutex AssetManager::SharedZip::gLock;
DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
-AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
- : mPath(path), mZipFile(NULL), mModWhen(modWhen),
- mResourceTableAsset(NULL), mResourceTable(NULL)
-{
- if (kIsDebug) {
- ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str());
- }
- ALOGV("+++ opening zip '%s'\n", mPath.c_str());
- mZipFile = ZipFileRO::open(mPath.c_str());
- if (mZipFile == NULL) {
- ALOGD("failed to open Zip archive '%s'\n", mPath.c_str());
- }
+AssetManager::SharedZip::SharedZip(const String8& path, ModDate modWhen)
+ : mPath(path),
+ mZipFile(NULL),
+ mModWhen(modWhen),
+ mResourceTableAsset(NULL),
+ mResourceTable(NULL) {
+ if (kIsDebug) {
+ ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str());
+ }
+ ALOGV("+++ opening zip '%s'\n", mPath.c_str());
+ mZipFile = ZipFileRO::open(mPath.c_str());
+ if (mZipFile == NULL) {
+ ALOGD("failed to open Zip archive '%s'\n", mPath.c_str());
+ }
}
AssetManager::SharedZip::SharedZip(int fd, const String8& path)
@@ -1453,7 +1455,7 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path,
bool createIfNotPresent)
{
AutoMutex _l(gLock);
- time_t modWhen = getFileModDate(path.c_str());
+ auto modWhen = getFileModDate(path.c_str());
sp<SharedZip> zip = gOpen.valueFor(path).promote();
if (zip != NULL && zip->mModWhen == modWhen) {
return zip;
@@ -1520,8 +1522,8 @@ ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res)
bool AssetManager::SharedZip::isUpToDate()
{
- time_t modWhen = getFileModDate(mPath.c_str());
- return mModWhen == modWhen;
+ auto modWhen = getFileModDate(mPath.c_str());
+ return mModWhen == modWhen;
}
void AssetManager::SharedZip::addOverlay(const asset_path& ap)
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 70d14a11830e..970463492c1a 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -16,8 +16,6 @@
#include <androidfw/TypeWrappers.h>
-#include <algorithm>
-
namespace android {
TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) {
@@ -31,30 +29,44 @@ TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(
ALOGE("Type's entry indices extend beyond its boundaries");
mLength = 0;
} else {
- mLength = ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx + 1;
+ mLength = dtohs(ResTable_sparseTypeEntry{entryIndices[entryCount - 1]}.idx) + 1;
}
}
}
TypeVariant::iterator& TypeVariant::iterator::operator++() {
- mIndex++;
+ ++mIndex;
if (mIndex > mTypeVariant->mLength) {
mIndex = mTypeVariant->mLength;
}
- return *this;
-}
-static bool keyCompare(uint32_t entry, uint16_t index) {
- return dtohs(ResTable_sparseTypeEntry{entry}.idx) < index;
+ const ResTable_type* type = mTypeVariant->data;
+ if ((type->flags & ResTable_type::FLAG_SPARSE) == 0) {
+ return *this;
+ }
+
+ // Need to adjust |mSparseIndex| as well if we've passed its current element.
+ const uint32_t entryCount = dtohl(type->entryCount);
+ const auto entryIndices = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
+ if (mSparseIndex >= entryCount) {
+ return *this; // done
+ }
+ const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex);
+ if (mIndex > dtohs(element->idx)) {
+ ++mSparseIndex;
+ }
+
+ return *this;
}
const ResTable_entry* TypeVariant::iterator::operator*() const {
- const ResTable_type* type = mTypeVariant->data;
if (mIndex >= mTypeVariant->mLength) {
- return NULL;
+ return nullptr;
}
- const uint32_t entryCount = dtohl(mTypeVariant->data->entryCount);
+ const ResTable_type* type = mTypeVariant->data;
+ const uint32_t entryCount = dtohl(type->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type)
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
@@ -63,18 +75,19 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
sizeof(uint16_t) : sizeof(uint32_t);
if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) {
ALOGE("Type's entry indices extend beyond its boundaries");
- return NULL;
+ return nullptr;
}
uint32_t entryOffset;
if (type->flags & ResTable_type::FLAG_SPARSE) {
- auto iter = std::lower_bound(entryIndices, entryIndices + entryCount, mIndex, keyCompare);
- if (iter == entryIndices + entryCount
- || dtohs(ResTable_sparseTypeEntry{*iter}.idx) != mIndex) {
- return NULL;
+ if (mSparseIndex >= entryCount) {
+ return nullptr;
}
-
- entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+ const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex);
+ if (dtohs(element->idx) != mIndex) {
+ return nullptr;
+ }
+ entryOffset = static_cast<uint32_t>(dtohs(element->offset)) * 4u;
} else if (type->flags & ResTable_type::FLAG_OFFSET16) {
auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices);
entryOffset = offset_from16(entryIndices16[mIndex]);
@@ -83,25 +96,25 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
}
if (entryOffset == ResTable_type::NO_ENTRY) {
- return NULL;
+ return nullptr;
}
if ((entryOffset & 0x3) != 0) {
ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset);
- return NULL;
+ return nullptr;
}
const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset);
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
- return NULL;
+ return nullptr;
} else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
- return NULL;
+ return nullptr;
} else if (entry->size() < sizeof(*entry)) {
ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
- return NULL;
+ return nullptr;
}
return entry;
}
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index ce0985b38986..376c881ea376 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -280,21 +280,21 @@ private:
~SharedZip();
private:
- SharedZip(const String8& path, time_t modWhen);
- SharedZip(int fd, const String8& path);
- SharedZip(); // <-- not implemented
+ SharedZip(const String8& path, ModDate modWhen);
+ SharedZip(int fd, const String8& path);
+ SharedZip(); // <-- not implemented
- String8 mPath;
- ZipFileRO* mZipFile;
- time_t mModWhen;
+ String8 mPath;
+ ZipFileRO* mZipFile;
+ ModDate mModWhen;
- Asset* mResourceTableAsset;
- ResTable* mResourceTable;
+ Asset* mResourceTableAsset;
+ ResTable* mResourceTable;
- Vector<asset_path> mOverlays;
+ Vector<asset_path> mOverlays;
- static Mutex gLock;
- static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
+ static Mutex gLock;
+ static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
};
/*
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index e213fbd22ab0..ac75eb3bb98c 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -25,8 +25,9 @@
#include "android-base/macros.h"
#include "android-base/unique_fd.h"
#include "androidfw/ConfigDescription.h"
-#include "androidfw/StringPiece.h"
#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
namespace android {
@@ -213,7 +214,7 @@ class LoadedIdmap {
android::base::unique_fd idmap_fd_;
std::string_view overlay_apk_path_;
std::string_view target_apk_path_;
- time_t idmap_last_mod_time_;
+ ModDate idmap_last_mod_time_;
private:
DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h
index fb2fad619011..db641b78a4e4 100644
--- a/libs/androidfw/include/androidfw/TypeWrappers.h
+++ b/libs/androidfw/include/androidfw/TypeWrappers.h
@@ -27,24 +27,14 @@ struct TypeVariant {
class iterator {
public:
- iterator& operator=(const iterator& rhs) {
- mTypeVariant = rhs.mTypeVariant;
- mIndex = rhs.mIndex;
- return *this;
- }
-
bool operator==(const iterator& rhs) const {
return mTypeVariant == rhs.mTypeVariant && mIndex == rhs.mIndex;
}
- bool operator!=(const iterator& rhs) const {
- return mTypeVariant != rhs.mTypeVariant || mIndex != rhs.mIndex;
- }
-
iterator operator++(int) {
- uint32_t prevIndex = mIndex;
+ iterator prev = *this;
operator++();
- return iterator(mTypeVariant, prevIndex);
+ return prev;
}
const ResTable_entry* operator->() const {
@@ -60,18 +50,26 @@ struct TypeVariant {
private:
friend struct TypeVariant;
- iterator(const TypeVariant* tv, uint32_t index)
- : mTypeVariant(tv), mIndex(index) {}
+
+ enum class Kind { Begin, End };
+ iterator(const TypeVariant* tv, Kind kind)
+ : mTypeVariant(tv) {
+ mSparseIndex = mIndex = kind == Kind::Begin ? 0 : tv->mLength;
+ // mSparseIndex here is technically past the number of sparse entries, but it is still
+ // ok as it is enough to infer that this is the end iterator.
+ }
+
const TypeVariant* mTypeVariant;
uint32_t mIndex;
+ uint32_t mSparseIndex;
};
iterator beginEntries() const {
- return iterator(this, 0);
+ return iterator(this, iterator::Kind::Begin);
}
iterator endEntries() const {
- return iterator(this, mLength);
+ return iterator(this, iterator::Kind::End);
}
const ResTable_type* data;
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 077609d20d55..c9ba8a01a5e9 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
-#include <sys/types.h>
+#include <time.h>
//
// Handy utility functions and portability code.
//
-#ifndef _LIBS_ANDROID_FW_MISC_H
-#define _LIBS_ANDROID_FW_MISC_H
namespace android {
@@ -41,15 +40,35 @@ typedef enum FileType {
} FileType;
/* get the file's type; follows symlinks */
FileType getFileType(const char* fileName);
-/* get the file's modification date; returns -1 w/errno set on failure */
-time_t getFileModDate(const char* fileName);
+
+// MinGW doesn't support nanosecond resolution in stat() modification time, and given
+// that it only matters on the device it's ok to keep it at a seconds level there.
+#ifdef _WIN32
+using ModDate = time_t;
+inline constexpr ModDate kInvalidModDate = ModDate(-1);
+inline constexpr unsigned long long kModDateResolutionNs = 1ull * 1000 * 1000 * 1000;
+inline time_t toTimeT(ModDate m) {
+ return m;
+}
+#else
+using ModDate = timespec;
+inline constexpr ModDate kInvalidModDate = {-1, -1};
+inline constexpr unsigned long long kModDateResolutionNs = 1;
+inline time_t toTimeT(ModDate m) {
+ return m.tv_sec;
+}
+#endif
+
+/* get the file's modification date; returns kInvalidModDate w/errno set on failure */
+ModDate getFileModDate(const char* fileName);
/* same, but also returns -1 if the file has already been deleted */
-time_t getFileModDate(int fd);
+ModDate getFileModDate(int fd);
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
bool isReadonlyFilesystem(int fd);
-}; // namespace android
+} // namespace android
-#endif // _LIBS_ANDROID_FW_MISC_H
+// Whoever uses getFileModDate() will need this as well
+bool operator==(const timespec& l, const timespec& r);
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 93dcaf549a90..32f3624a3aee 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -28,11 +28,13 @@
#include <sys/vfs.h>
#endif // __linux__
-#include <cstring>
-#include <cstdio>
#include <errno.h>
#include <sys/stat.h>
+#include <cstdio>
+#include <cstring>
+#include <tuple>
+
namespace android {
/*
@@ -73,27 +75,34 @@ FileType getFileType(const char* fileName)
}
}
-/*
- * Get a file's modification date.
- */
-time_t getFileModDate(const char* fileName) {
- struct stat sb;
- if (stat(fileName, &sb) < 0) {
- return (time_t)-1;
- }
- return sb.st_mtime;
+static ModDate getModDate(const struct stat& st) {
+#ifdef _WIN32
+ return st.st_mtime;
+#elif defined(__APPLE__)
+ return st.st_mtimespec;
+#else
+ return st.st_mtim;
+#endif
}
-time_t getFileModDate(int fd) {
- struct stat sb;
- if (fstat(fd, &sb) < 0) {
- return (time_t)-1;
- }
- if (sb.st_nlink <= 0) {
- errno = ENOENT;
- return (time_t)-1;
- }
- return sb.st_mtime;
+ModDate getFileModDate(const char* fileName) {
+ struct stat sb;
+ if (stat(fileName, &sb) < 0) {
+ return kInvalidModDate;
+ }
+ return getModDate(sb);
+}
+
+ModDate getFileModDate(int fd) {
+ struct stat sb;
+ if (fstat(fd, &sb) < 0) {
+ return kInvalidModDate;
+ }
+ if (sb.st_nlink <= 0) {
+ errno = ENOENT;
+ return kInvalidModDate;
+ }
+ return getModDate(sb);
}
#ifndef __linux__
@@ -124,4 +133,8 @@ bool isReadonlyFilesystem(int fd) {
}
#endif // __linux__
-}; // namespace android
+} // namespace android
+
+bool operator==(const timespec& l, const timespec& r) {
+ return std::tie(l.tv_sec, l.tv_nsec) == std::tie(r.tv_sec, l.tv_nsec);
+}
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 60aa7d88925d..cb2e56f5f5e4 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include <chrono>
+#include <thread>
+
#include "android-base/file.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/AssetManager2.h"
@@ -27,6 +30,7 @@
#include "data/overlayable/R.h"
#include "data/system/R.h"
+using namespace std::chrono_literals;
using ::testing::NotNull;
namespace overlay = com::android::overlay;
@@ -218,10 +222,13 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
unlink(temp_file.path);
ASSERT_FALSE(apk_assets->IsUpToDate());
- sleep(2);
+
+ const auto sleep_duration =
+ std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
+ std::this_thread::sleep_for(sleep_duration);
base::WriteStringToFile("hello", temp_file.path);
- sleep(2);
+ std::this_thread::sleep_for(sleep_duration);
ASSERT_FALSE(apk_assets->IsUpToDate());
}
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index ed30904ec179..d66e05805484 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -14,28 +14,42 @@
* limitations under the License.
*/
-#include <algorithm>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
-#include <utils/String8.h>
+#include <androidfw/Util.h>
+
+#include <optional>
+#include <vector>
#include <gtest/gtest.h>
namespace android {
-// create a ResTable_type in memory with a vector of Res_value*
-static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
- bool compact_entry = false,
- bool short_offsets = false)
+using ResValueVector = std::vector<std::optional<Res_value>>;
+
+// create a ResTable_type in memory
+static util::unique_cptr<ResTable_type> createTypeTable(
+ const ResValueVector& in_values, bool compact_entry, bool short_offsets, bool sparse)
{
+ ResValueVector sparse_values;
+ if (sparse) {
+ std::ranges::copy_if(in_values, std::back_inserter(sparse_values),
+ [](auto&& v) { return v.has_value(); });
+ }
+ const ResValueVector& values = sparse ? sparse_values : in_values;
+
ResTable_type t{};
t.header.type = RES_TABLE_TYPE_TYPE;
t.header.headerSize = sizeof(t);
t.header.size = sizeof(t);
t.id = 1;
- t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0;
+ t.flags = sparse
+ ? ResTable_type::FLAG_SPARSE
+ : short_offsets ? ResTable_type::FLAG_OFFSET16 : 0;
- t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t));
+ t.header.size += values.size() *
+ (sparse ? sizeof(ResTable_sparseTypeEntry) :
+ short_offsets ? sizeof(uint16_t) : sizeof(uint32_t));
t.entriesStart = t.header.size;
t.entryCount = values.size();
@@ -53,9 +67,18 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
memcpy(p_header, &t, sizeof(t));
size_t i = 0, entry_offset = 0;
- uint32_t k = 0;
- for (auto const& v : values) {
- if (short_offsets) {
+ uint32_t sparse_index = 0;
+
+ for (auto const& v : in_values) {
+ if (sparse) {
+ if (!v) {
+ ++i;
+ continue;
+ }
+ const auto p = reinterpret_cast<ResTable_sparseTypeEntry*>(p_offsets) + sparse_index++;
+ p->idx = i;
+ p->offset = (entry_offset >> 2) & 0xffffu;
+ } else if (short_offsets) {
uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i;
*p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu;
} else {
@@ -83,62 +106,92 @@ static ResTable_type* createTypeTable(std::vector<Res_value*>& values,
}
i++;
}
- return reinterpret_cast<ResTable_type*>(data);
+ return util::unique_cptr<ResTable_type>{reinterpret_cast<ResTable_type*>(data)};
}
TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
- std::vector<Res_value *> values;
-
- Res_value *v1 = new Res_value{};
- values.push_back(v1);
-
- values.push_back(nullptr);
-
- Res_value *v2 = new Res_value{};
- values.push_back(v2);
-
- Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678};
- values.push_back(v3);
+ ResValueVector values;
+
+ values.push_back(std::nullopt);
+ values.push_back(Res_value{});
+ values.push_back(std::nullopt);
+ values.push_back(Res_value{});
+ values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678});
+ values.push_back(std::nullopt);
+ values.push_back(std::nullopt);
+ values.push_back(std::nullopt);
+ values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x87654321});
// test for combinations of compact_entry and short_offsets
- for (size_t i = 0; i < 4; i++) {
- bool compact_entry = i & 0x1, short_offsets = i & 0x2;
- ResTable_type* data = createTypeTable(values, compact_entry, short_offsets);
- TypeVariant v(data);
+ for (size_t i = 0; i < 8; i++) {
+ bool compact_entry = i & 0x1, short_offsets = i & 0x2, sparse = i & 0x4;
+ auto data = createTypeTable(values, compact_entry, short_offsets, sparse);
+ TypeVariant v(data.get());
TypeVariant::iterator iter = v.beginEntries();
ASSERT_EQ(uint32_t(0), iter.index());
- ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key());
+ ASSERT_TRUE(NULL == *iter);
ASSERT_NE(v.endEntries(), iter);
- iter++;
+ ++iter;
ASSERT_EQ(uint32_t(1), iter.index());
- ASSERT_TRUE(NULL == *iter);
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(1), iter->key());
ASSERT_NE(v.endEntries(), iter);
iter++;
ASSERT_EQ(uint32_t(2), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
+
+ ++iter;
+
+ ASSERT_EQ(uint32_t(3), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(2), iter->key());
+ ASSERT_EQ(uint32_t(3), iter->key());
ASSERT_NE(v.endEntries(), iter);
iter++;
- ASSERT_EQ(uint32_t(3), iter.index());
+ ASSERT_EQ(uint32_t(4), iter.index());
ASSERT_TRUE(NULL != *iter);
ASSERT_EQ(iter->is_compact(), compact_entry);
- ASSERT_EQ(uint32_t(3), iter->key());
+ ASSERT_EQ(uint32_t(4), iter->key());
ASSERT_EQ(uint32_t(0x12345678), iter->value().data);
ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType);
+ ++iter;
+
+ ASSERT_EQ(uint32_t(5), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
+
+ ++iter;
+
+ ASSERT_EQ(uint32_t(6), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
+
+ ++iter;
+
+ ASSERT_EQ(uint32_t(7), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
+
iter++;
- ASSERT_EQ(v.endEntries(), iter);
+ ASSERT_EQ(uint32_t(8), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(iter->is_compact(), compact_entry);
+ ASSERT_EQ(uint32_t(8), iter->key());
+ ASSERT_EQ(uint32_t(0x87654321), iter->value().data);
+ ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType);
- free(data);
+ ++iter;
+
+ ASSERT_EQ(v.endEntries(), iter);
}
}
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index 8af4b7e8f4c8..4fecf4de0312 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -59,7 +59,6 @@ cc_library {
apex_available: [
"//apex_available:platform",
"com.android.os.statsd",
- "test_com.android.os.statsd",
"com.android.uprobestats",
],
}
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 0c2f3adc2838..eb19ba84ee62 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -1,6 +1,29 @@
// Signature format: 2.0
package android.location {
+ @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class AuxiliaryInformation implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.location.GnssSignalType> getAvailableSignalTypes();
+ method @IntRange(from=0xfffffff9, to=6) public int getFrequencyChannelNumber();
+ method public int getSatType();
+ method @IntRange(from=1) public int getSvid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BDS_B1C_ORBIT_TYPE_GEO = 1; // 0x1
+ field public static final int BDS_B1C_ORBIT_TYPE_IGSO = 2; // 0x2
+ field public static final int BDS_B1C_ORBIT_TYPE_MEO = 3; // 0x3
+ field public static final int BDS_B1C_ORBIT_TYPE_UNDEFINED = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.location.AuxiliaryInformation> CREATOR;
+ }
+
+ public static final class AuxiliaryInformation.Builder {
+ ctor public AuxiliaryInformation.Builder();
+ method @NonNull public android.location.AuxiliaryInformation build();
+ method @NonNull public android.location.AuxiliaryInformation.Builder setAvailableSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+ method @NonNull public android.location.AuxiliaryInformation.Builder setFrequencyChannelNumber(@IntRange(from=0xfffffff9, to=6) int);
+ method @NonNull public android.location.AuxiliaryInformation.Builder setSatType(int);
+ method @NonNull public android.location.AuxiliaryInformation.Builder setSvid(@IntRange(from=1) int);
+ }
+
public abstract class BatchedLocationCallback {
ctor public BatchedLocationCallback();
method public void onLocationBatch(java.util.List<android.location.Location>);
@@ -9,6 +32,7 @@ package android.location {
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouAssistance implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.GnssAlmanac getAlmanac();
+ method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -24,12 +48,13 @@ package android.location {
ctor public BeidouAssistance.Builder();
method @NonNull public android.location.BeidouAssistance build();
method @NonNull public android.location.BeidouAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+ method @NonNull public android.location.BeidouAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
method @NonNull public android.location.BeidouAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
method @NonNull public android.location.BeidouAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
- method @NonNull public android.location.BeidouAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
- method @NonNull public android.location.BeidouAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
- method @NonNull public android.location.BeidouAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.BeidouSatelliteEphemeris>);
- method @NonNull public android.location.BeidouAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+ method @NonNull public android.location.BeidouAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+ method @NonNull public android.location.BeidouAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+ method @NonNull public android.location.BeidouAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.BeidouSatelliteEphemeris>);
+ method @NonNull public android.location.BeidouAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
method @NonNull public android.location.BeidouAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
}
@@ -151,6 +176,7 @@ package android.location {
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoAssistance implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.GnssAlmanac getAlmanac();
+ method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -166,12 +192,13 @@ package android.location {
ctor public GalileoAssistance.Builder();
method @NonNull public android.location.GalileoAssistance build();
method @NonNull public android.location.GalileoAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+ method @NonNull public android.location.GalileoAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
method @NonNull public android.location.GalileoAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
method @NonNull public android.location.GalileoAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
- method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
- method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
- method @NonNull public android.location.GalileoAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GalileoSatelliteEphemeris>);
- method @NonNull public android.location.GalileoAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+ method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+ method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+ method @NonNull public android.location.GalileoAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GalileoSatelliteEphemeris>);
+ method @NonNull public android.location.GalileoAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
method @NonNull public android.location.GalileoAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
}
@@ -319,6 +346,7 @@ package android.location {
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAssistance implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.GlonassAlmanac getAlmanac();
+ method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections();
method @NonNull public java.util.List<android.location.GlonassSatelliteEphemeris> getSatelliteEphemeris();
method @NonNull public java.util.List<android.location.TimeModel> getTimeModels();
@@ -331,9 +359,10 @@ package android.location {
ctor public GlonassAssistance.Builder();
method @NonNull public android.location.GlonassAssistance build();
method @NonNull public android.location.GlonassAssistance.Builder setAlmanac(@Nullable android.location.GlonassAlmanac);
- method @NonNull public android.location.GlonassAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
- method @NonNull public android.location.GlonassAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GlonassSatelliteEphemeris>);
- method @NonNull public android.location.GlonassAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+ method @NonNull public android.location.GlonassAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
+ method @NonNull public android.location.GlonassAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+ method @NonNull public android.location.GlonassAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GlonassSatelliteEphemeris>);
+ method @NonNull public android.location.GlonassAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
method @NonNull public android.location.GlonassAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
}
@@ -688,6 +717,7 @@ package android.location {
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsAssistance implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.GnssAlmanac getAlmanac();
+ method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -703,12 +733,13 @@ package android.location {
ctor public GpsAssistance.Builder();
method @NonNull public android.location.GpsAssistance build();
method @NonNull public android.location.GpsAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+ method @NonNull public android.location.GpsAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
method @NonNull public android.location.GpsAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
method @NonNull public android.location.GpsAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
- method @NonNull public android.location.GpsAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
- method @NonNull public android.location.GpsAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
- method @NonNull public android.location.GpsAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GpsSatelliteEphemeris>);
- method @NonNull public android.location.GpsAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+ method @NonNull public android.location.GpsAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+ method @NonNull public android.location.GpsAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+ method @NonNull public android.location.GpsAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.GpsSatelliteEphemeris>);
+ method @NonNull public android.location.GpsAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
method @NonNull public android.location.GpsAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
}
@@ -1222,6 +1253,7 @@ package android.location {
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssAssistance implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.location.GnssAlmanac getAlmanac();
+ method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
@@ -1237,12 +1269,13 @@ package android.location {
ctor public QzssAssistance.Builder();
method @NonNull public android.location.QzssAssistance build();
method @NonNull public android.location.QzssAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
+ method @NonNull public android.location.QzssAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
method @NonNull public android.location.QzssAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
method @NonNull public android.location.QzssAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
- method @NonNull public android.location.QzssAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>);
- method @NonNull public android.location.QzssAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
- method @NonNull public android.location.QzssAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.QzssSatelliteEphemeris>);
- method @NonNull public android.location.QzssAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>);
+ method @NonNull public android.location.QzssAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
+ method @NonNull public android.location.QzssAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
+ method @NonNull public android.location.QzssAssistance.Builder setSatelliteEphemeris(@NonNull java.util.List<android.location.QzssSatelliteEphemeris>);
+ method @NonNull public android.location.QzssAssistance.Builder setTimeModels(@NonNull java.util.List<android.location.TimeModel>);
method @NonNull public android.location.QzssAssistance.Builder setUtcModel(@Nullable android.location.UtcModel);
}
@@ -1273,11 +1306,11 @@ package android.location {
method public int describeContents();
method @NonNull public String getAdvisoryNumber();
method @NonNull public String getAdvisoryType();
+ method @NonNull public java.util.List<android.location.GnssSignalType> getBadSignalTypes();
+ method @IntRange(from=1, to=206) public int getBadSvid();
method @IntRange(from=0) public long getEndDateSeconds();
method @IntRange(from=0) public long getPublishDateSeconds();
method @IntRange(from=0) public long getStartDateSeconds();
- method @IntRange(from=1, to=206) public int getSvid();
- method public boolean isUsable();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.RealTimeIntegrityModel> CREATOR;
}
@@ -1287,11 +1320,11 @@ package android.location {
method @NonNull public android.location.RealTimeIntegrityModel build();
method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryNumber(@NonNull String);
method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryType(@NonNull String);
+ method @NonNull public android.location.RealTimeIntegrityModel.Builder setBadSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
+ method @NonNull public android.location.RealTimeIntegrityModel.Builder setBadSvid(@IntRange(from=1, to=206) int);
method @NonNull public android.location.RealTimeIntegrityModel.Builder setEndDateSeconds(@IntRange(from=0) long);
method @NonNull public android.location.RealTimeIntegrityModel.Builder setPublishDateSeconds(@IntRange(from=0) long);
method @NonNull public android.location.RealTimeIntegrityModel.Builder setStartDateSeconds(@IntRange(from=0) long);
- method @NonNull public android.location.RealTimeIntegrityModel.Builder setSvid(@IntRange(from=1, to=206) int);
- method @NonNull public android.location.RealTimeIntegrityModel.Builder setUsable(boolean);
}
@FlaggedApi("android.location.flags.gnss_assistance_interface") public final class SatelliteEphemerisTime implements android.os.Parcelable {
@@ -1459,6 +1492,13 @@ package android.location.provider {
field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider";
}
+ @FlaggedApi("android.location.flags.gnss_assistance_interface") public abstract class GnssAssistanceProviderBase {
+ ctor public GnssAssistanceProviderBase(@NonNull android.content.Context, @NonNull String);
+ method @NonNull public final android.os.IBinder getBinder();
+ method public abstract void onRequest(@NonNull android.os.OutcomeReceiver<android.location.GnssAssistance,java.lang.Throwable>);
+ field public static final String ACTION_GNSS_ASSISTANCE_PROVIDER = "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER";
+ }
+
public abstract class LocationProviderBase {
ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties);
method @Nullable public final android.os.IBinder getBinder();
diff --git a/location/java/android/location/AuxiliaryInformation.java b/location/java/android/location/AuxiliaryInformation.java
new file mode 100644
index 000000000000..601c87e69b70
--- /dev/null
+++ b/location/java/android/location/AuxiliaryInformation.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.location.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class contains parameters to provide additional assistance information dependent on the GNSS
+ * constellation.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE)
+@SystemApi
+public final class AuxiliaryInformation implements Parcelable {
+
+ /**
+ * BDS B1C Satellite orbit type.
+ *
+ * <p>This is defined in BDS-SIS-ICD-B1I-3.0, section 3.1.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ BDS_B1C_ORBIT_TYPE_UNDEFINED,
+ BDS_B1C_ORBIT_TYPE_GEO,
+ BDS_B1C_ORBIT_TYPE_IGSO,
+ BDS_B1C_ORBIT_TYPE_MEO
+ })
+ public @interface BeidouB1CSatelliteOrbitType {}
+
+ /**
+ * The following enumerations must be in sync with the values declared in
+ * AuxiliaryInformation.aidl.
+ */
+
+ /** The orbit type is undefined. */
+ public static final int BDS_B1C_ORBIT_TYPE_UNDEFINED = 0;
+
+ /** The orbit type is GEO. */
+ public static final int BDS_B1C_ORBIT_TYPE_GEO = 1;
+
+ /** The orbit type is IGSO. */
+ public static final int BDS_B1C_ORBIT_TYPE_IGSO = 2;
+
+ /** The orbit type is MEO. */
+ public static final int BDS_B1C_ORBIT_TYPE_MEO = 3;
+
+ /**
+ * Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle (SV), or OSN
+ * number for Glonass.
+ *
+ * <p>The distinction is made by looking at the constellation field. Values must be in the range
+ * of:
+ *
+ * <p>- GPS: 1-32
+ *
+ * <p>- GLONASS: 1-25
+ *
+ * <p>- QZSS: 183-206
+ *
+ * <p>- Galileo: 1-36
+ *
+ * <p>- Beidou: 1-63
+ */
+ private final int mSvid;
+
+ /** The list of available signal types for the satellite. */
+ @NonNull private final List<GnssSignalType> mAvailableSignalTypes;
+
+ /**
+ * Glonass carrier frequency number of the satellite. This is required for Glonass.
+ *
+ * <p>This is defined in Glonass ICD v5.1 section 3.3.1.1.
+ */
+ private final int mFrequencyChannelNumber;
+
+ /** BDS B1C satellite orbit type. This is required for Beidou. */
+ private final @BeidouB1CSatelliteOrbitType int mSatType;
+
+ private AuxiliaryInformation(Builder builder) {
+ // Allow Svid beyond the range to support potential future extensibility.
+ Preconditions.checkArgument(builder.mSvid >= 1);
+ Preconditions.checkNotNull(
+ builder.mAvailableSignalTypes, "AvailableSignalTypes cannot be null");
+ Preconditions.checkArgument(builder.mAvailableSignalTypes.size() > 0);
+ Preconditions.checkArgumentInRange(
+ builder.mFrequencyChannelNumber, -7, 6, "FrequencyChannelNumber");
+ Preconditions.checkArgumentInRange(
+ builder.mSatType, BDS_B1C_ORBIT_TYPE_UNDEFINED, BDS_B1C_ORBIT_TYPE_MEO, "SatType");
+ mSvid = builder.mSvid;
+ mAvailableSignalTypes =
+ Collections.unmodifiableList(new ArrayList<>(builder.mAvailableSignalTypes));
+ mFrequencyChannelNumber = builder.mFrequencyChannelNumber;
+ mSatType = builder.mSatType;
+ }
+
+ /**
+ * Returns the Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle
+ * (SV), or OSN number for Glonass.
+ *
+ * <p>The distinction is made by looking at the constellation field. Values must be in the range
+ * of:
+ *
+ * <p>- GPS: 1-32
+ *
+ * <p>- GLONASS: 1-25
+ *
+ * <p>- QZSS: 183-206
+ *
+ * <p>- Galileo: 1-36
+ *
+ * <p>- Beidou: 1-63
+ */
+ @IntRange(from = 1)
+ public int getSvid() {
+ return mSvid;
+ }
+
+ /** Returns the list of available signal types for the satellite. */
+ @NonNull
+ public List<GnssSignalType> getAvailableSignalTypes() {
+ return mAvailableSignalTypes;
+ }
+
+ /** Returns the Glonass carrier frequency number of the satellite. */
+ @IntRange(from = -7, to = 6)
+ public int getFrequencyChannelNumber() {
+ return mFrequencyChannelNumber;
+ }
+
+ /** Returns the BDS B1C satellite orbit type. */
+ @BeidouB1CSatelliteOrbitType
+ public int getSatType() {
+ return mSatType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSvid);
+ dest.writeTypedList(mAvailableSignalTypes);
+ dest.writeInt(mFrequencyChannelNumber);
+ dest.writeInt(mSatType);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ StringBuilder builder = new StringBuilder("AuxiliaryInformation[");
+ builder.append("svid = ").append(mSvid);
+ builder.append(", availableSignalTypes = ").append(mAvailableSignalTypes);
+ builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber);
+ builder.append(", satType = ").append(mSatType);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final @NonNull Parcelable.Creator<AuxiliaryInformation> CREATOR =
+ new Parcelable.Creator<AuxiliaryInformation>() {
+ @Override
+ public AuxiliaryInformation createFromParcel(@NonNull Parcel in) {
+ return new AuxiliaryInformation.Builder()
+ .setSvid(in.readInt())
+ .setAvailableSignalTypes(
+ in.createTypedArrayList(GnssSignalType.CREATOR))
+ .setFrequencyChannelNumber(in.readInt())
+ .setSatType(in.readInt())
+ .build();
+ }
+
+ @Override
+ public AuxiliaryInformation[] newArray(int size) {
+ return new AuxiliaryInformation[size];
+ }
+ };
+
+ /** A builder class for {@link AuxiliaryInformation}. */
+ public static final class Builder {
+ private int mSvid;
+ private List<GnssSignalType> mAvailableSignalTypes;
+ private int mFrequencyChannelNumber;
+ private @BeidouB1CSatelliteOrbitType int mSatType;
+
+ /**
+ * Sets the Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle
+ * (SV), or OSN number for Glonass.
+ *
+ * <p>The distinction is made by looking at the constellation field. Values must be in the
+ * range of:
+ *
+ * <p>- GPS: 1-32
+ *
+ * <p>- GLONASS: 1-25
+ *
+ * <p>- QZSS: 183-206
+ *
+ * <p>- Galileo: 1-36
+ *
+ * <p>- Beidou: 1-63
+ */
+ @NonNull
+ public Builder setSvid(@IntRange(from = 1) int svid) {
+ mSvid = svid;
+ return this;
+ }
+
+ /**
+ * Sets the list of available signal types for the satellite.
+ *
+ * <p>The list must be set and cannot be an empty list.
+ */
+ @NonNull
+ public Builder setAvailableSignalTypes(@NonNull List<GnssSignalType> availableSignalTypes) {
+ mAvailableSignalTypes = availableSignalTypes;
+ return this;
+ }
+
+ /** Sets the Glonass carrier frequency number of the satellite. */
+ @NonNull
+ public Builder setFrequencyChannelNumber(
+ @IntRange(from = -7, to = 6) int frequencyChannelNumber) {
+ mFrequencyChannelNumber = frequencyChannelNumber;
+ return this;
+ }
+
+ /** Sets the BDS B1C satellite orbit type. */
+ @NonNull
+ public Builder setSatType(@BeidouB1CSatelliteOrbitType int satType) {
+ mSatType = satType;
+ return this;
+ }
+
+ /** Builds a {@link AuxiliaryInformation} instance as specified by this builder. */
+ @NonNull
+ public AuxiliaryInformation build() {
+ return new AuxiliaryInformation(this);
+ }
+ }
+}
diff --git a/location/java/android/location/BeidouAssistance.java b/location/java/android/location/BeidouAssistance.java
index f55249e605a0..e35493ed1007 100644
--- a/location/java/android/location/BeidouAssistance.java
+++ b/location/java/android/location/BeidouAssistance.java
@@ -19,7 +19,6 @@ package android.location;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.location.GnssAssistance.GnssSatelliteCorrections;
import android.location.flags.Flags;
@@ -51,6 +50,9 @@ public final class BeidouAssistance implements Parcelable {
/** The leap seconds model. */
@Nullable private final LeapSecondsModel mLeapSecondsModel;
+ /** The auxiliary information. */
+ @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
/** The list of time models. */
@NonNull private final List<TimeModel> mTimeModels;
@@ -68,6 +70,7 @@ public final class BeidouAssistance implements Parcelable {
mIonosphericModel = builder.mIonosphericModel;
mUtcModel = builder.mUtcModel;
mLeapSecondsModel = builder.mLeapSecondsModel;
+ mAuxiliaryInformation = builder.mAuxiliaryInformation;
if (builder.mTimeModels != null) {
mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
} else {
@@ -117,6 +120,12 @@ public final class BeidouAssistance implements Parcelable {
return mLeapSecondsModel;
}
+ /** Returns the auxiliary information. */
+ @Nullable
+ public AuxiliaryInformation getAuxiliaryInformation() {
+ return mAuxiliaryInformation;
+ }
+
/** Returns the list of time models. */
@NonNull
public List<TimeModel> getTimeModels() {
@@ -154,6 +163,7 @@ public final class BeidouAssistance implements Parcelable {
builder.append(", ionosphericModel = ").append(mIonosphericModel);
builder.append(", utcModel = ").append(mUtcModel);
builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+ builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
builder.append(", timeModels = ").append(mTimeModels);
builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -168,6 +178,7 @@ public final class BeidouAssistance implements Parcelable {
dest.writeTypedObject(mIonosphericModel, flags);
dest.writeTypedObject(mUtcModel, flags);
dest.writeTypedObject(mLeapSecondsModel, flags);
+ dest.writeTypedObject(mAuxiliaryInformation, flags);
dest.writeTypedList(mTimeModels);
dest.writeTypedList(mSatelliteEphemeris);
dest.writeTypedList(mRealTimeIntegrityModels);
@@ -184,6 +195,8 @@ public final class BeidouAssistance implements Parcelable {
in.readTypedObject(KlobucharIonosphericModel.CREATOR))
.setUtcModel(in.readTypedObject(UtcModel.CREATOR))
.setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+ .setAuxiliaryInformation(
+ in.readTypedObject(AuxiliaryInformation.CREATOR))
.setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
.setSatelliteEphemeris(
in.createTypedArrayList(BeidouSatelliteEphemeris.CREATOR))
@@ -206,6 +219,7 @@ public final class BeidouAssistance implements Parcelable {
private KlobucharIonosphericModel mIonosphericModel;
private UtcModel mUtcModel;
private LeapSecondsModel mLeapSecondsModel;
+ private AuxiliaryInformation mAuxiliaryInformation;
private List<TimeModel> mTimeModels;
private List<BeidouSatelliteEphemeris> mSatelliteEphemeris;
private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -239,10 +253,17 @@ public final class BeidouAssistance implements Parcelable {
return this;
}
+ /** Sets the auxiliary information. */
+ @NonNull
+ public Builder setAuxiliaryInformation(
+ @Nullable AuxiliaryInformation auxiliaryInformation) {
+ mAuxiliaryInformation = auxiliaryInformation;
+ return this;
+ }
+
/** Sets the list of time models. */
@NonNull
- public Builder setTimeModels(
- @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+ public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
mTimeModels = timeModels;
return this;
}
@@ -250,8 +271,7 @@ public final class BeidouAssistance implements Parcelable {
/** Sets the list of Beidou ephemeris. */
@NonNull
public Builder setSatelliteEphemeris(
- @Nullable @SuppressLint("NullableCollection")
- List<BeidouSatelliteEphemeris> satelliteEphemeris) {
+ @NonNull List<BeidouSatelliteEphemeris> satelliteEphemeris) {
mSatelliteEphemeris = satelliteEphemeris;
return this;
}
@@ -259,8 +279,7 @@ public final class BeidouAssistance implements Parcelable {
/** Sets the list of real time integrity models. */
@NonNull
public Builder setRealTimeIntegrityModels(
- @Nullable @SuppressLint("NullableCollection")
- List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+ @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
mRealTimeIntegrityModels = realTimeIntegrityModels;
return this;
}
@@ -268,8 +287,7 @@ public final class BeidouAssistance implements Parcelable {
/** Sets the list of Beidou satellite corrections. */
@NonNull
public Builder setSatelliteCorrections(
- @Nullable @SuppressLint("NullableCollection")
- List<GnssSatelliteCorrections> satelliteCorrections) {
+ @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
mSatelliteCorrections = satelliteCorrections;
return this;
}
diff --git a/location/java/android/location/GalileoAssistance.java b/location/java/android/location/GalileoAssistance.java
index 07c5bab856db..8a09e6634d09 100644
--- a/location/java/android/location/GalileoAssistance.java
+++ b/location/java/android/location/GalileoAssistance.java
@@ -19,7 +19,6 @@ package android.location;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.location.GnssAssistance.GnssSatelliteCorrections;
import android.location.flags.Flags;
@@ -51,6 +50,9 @@ public final class GalileoAssistance implements Parcelable {
/** The leap seconds model. */
@Nullable private final LeapSecondsModel mLeapSecondsModel;
+ /** The auxiliary information. */
+ @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
/** The list of time models. */
@NonNull private final List<TimeModel> mTimeModels;
@@ -68,6 +70,7 @@ public final class GalileoAssistance implements Parcelable {
mIonosphericModel = builder.mIonosphericModel;
mUtcModel = builder.mUtcModel;
mLeapSecondsModel = builder.mLeapSecondsModel;
+ mAuxiliaryInformation = builder.mAuxiliaryInformation;
if (builder.mTimeModels != null) {
mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
} else {
@@ -117,6 +120,12 @@ public final class GalileoAssistance implements Parcelable {
return mLeapSecondsModel;
}
+ /** Returns the auxiliary information. */
+ @Nullable
+ public AuxiliaryInformation getAuxiliaryInformation() {
+ return mAuxiliaryInformation;
+ }
+
/** Returns the list of time models. */
@NonNull
public List<TimeModel> getTimeModels() {
@@ -152,6 +161,7 @@ public final class GalileoAssistance implements Parcelable {
dest.writeTypedObject(mIonosphericModel, flags);
dest.writeTypedObject(mUtcModel, flags);
dest.writeTypedObject(mLeapSecondsModel, flags);
+ dest.writeTypedObject(mAuxiliaryInformation, flags);
dest.writeTypedList(mTimeModels);
dest.writeTypedList(mSatelliteEphemeris);
dest.writeTypedList(mRealTimeIntegrityModels);
@@ -166,6 +176,7 @@ public final class GalileoAssistance implements Parcelable {
builder.append(", ionosphericModel = ").append(mIonosphericModel);
builder.append(", utcModel = ").append(mUtcModel);
builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+ builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
builder.append(", timeModels = ").append(mTimeModels);
builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -184,6 +195,8 @@ public final class GalileoAssistance implements Parcelable {
in.readTypedObject(KlobucharIonosphericModel.CREATOR))
.setUtcModel(in.readTypedObject(UtcModel.CREATOR))
.setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+ .setAuxiliaryInformation(
+ in.readTypedObject(AuxiliaryInformation.CREATOR))
.setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
.setSatelliteEphemeris(
in.createTypedArrayList(GalileoSatelliteEphemeris.CREATOR))
@@ -206,6 +219,7 @@ public final class GalileoAssistance implements Parcelable {
private KlobucharIonosphericModel mIonosphericModel;
private UtcModel mUtcModel;
private LeapSecondsModel mLeapSecondsModel;
+ private AuxiliaryInformation mAuxiliaryInformation;
private List<TimeModel> mTimeModels;
private List<GalileoSatelliteEphemeris> mSatelliteEphemeris;
private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -239,10 +253,17 @@ public final class GalileoAssistance implements Parcelable {
return this;
}
+ /** Sets the auxiliary information. */
+ @NonNull
+ public Builder setAuxiliaryInformation(
+ @Nullable AuxiliaryInformation auxiliaryInformation) {
+ mAuxiliaryInformation = auxiliaryInformation;
+ return this;
+ }
+
/** Sets the list of time models. */
@NonNull
- public Builder setTimeModels(
- @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+ public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
mTimeModels = timeModels;
return this;
}
@@ -250,8 +271,7 @@ public final class GalileoAssistance implements Parcelable {
/** Sets the list of Galileo ephemeris. */
@NonNull
public Builder setSatelliteEphemeris(
- @Nullable @SuppressLint("NullableCollection")
- List<GalileoSatelliteEphemeris> satelliteEphemeris) {
+ @NonNull List<GalileoSatelliteEphemeris> satelliteEphemeris) {
mSatelliteEphemeris = satelliteEphemeris;
return this;
}
@@ -259,8 +279,7 @@ public final class GalileoAssistance implements Parcelable {
/** Sets the list of real time integrity models. */
@NonNull
public Builder setRealTimeIntegrityModels(
- @Nullable @SuppressLint("NullableCollection")
- List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+ @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
mRealTimeIntegrityModels = realTimeIntegrityModels;
return this;
}
@@ -268,8 +287,7 @@ public final class GalileoAssistance implements Parcelable {
/** Sets the list of Galileo satellite corrections. */
@NonNull
public Builder setSatelliteCorrections(
- @Nullable @SuppressLint("NullableCollection")
- List<GnssSatelliteCorrections> satelliteCorrections) {
+ @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
mSatelliteCorrections = satelliteCorrections;
return this;
}
diff --git a/location/java/android/location/GlonassAssistance.java b/location/java/android/location/GlonassAssistance.java
index cc0820197d8d..c7ed1c52b403 100644
--- a/location/java/android/location/GlonassAssistance.java
+++ b/location/java/android/location/GlonassAssistance.java
@@ -19,7 +19,6 @@ package android.location;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.location.GnssAssistance.GnssSatelliteCorrections;
import android.location.flags.Flags;
@@ -45,6 +44,9 @@ public final class GlonassAssistance implements Parcelable {
/** The UTC model. */
@Nullable private final UtcModel mUtcModel;
+ /** The auxiliary information. */
+ @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
/** The list of time models. */
@NonNull private final List<TimeModel> mTimeModels;
@@ -57,6 +59,7 @@ public final class GlonassAssistance implements Parcelable {
private GlonassAssistance(Builder builder) {
mAlmanac = builder.mAlmanac;
mUtcModel = builder.mUtcModel;
+ mAuxiliaryInformation = builder.mAuxiliaryInformation;
if (builder.mTimeModels != null) {
mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
} else {
@@ -106,6 +109,12 @@ public final class GlonassAssistance implements Parcelable {
return mSatelliteCorrections;
}
+ /** Returns the auxiliary information. */
+ @Nullable
+ public AuxiliaryInformation getAuxiliaryInformation() {
+ return mAuxiliaryInformation;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -115,6 +124,7 @@ public final class GlonassAssistance implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mAlmanac, flags);
dest.writeTypedObject(mUtcModel, flags);
+ dest.writeTypedObject(mAuxiliaryInformation, flags);
dest.writeTypedList(mTimeModels);
dest.writeTypedList(mSatelliteEphemeris);
dest.writeTypedList(mSatelliteCorrections);
@@ -126,6 +136,7 @@ public final class GlonassAssistance implements Parcelable {
StringBuilder builder = new StringBuilder("GlonassAssistance[");
builder.append("almanac = ").append(mAlmanac);
builder.append(", utcModel = ").append(mUtcModel);
+ builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
builder.append(", timeModels = ").append(mTimeModels);
builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
builder.append(", satelliteCorrections = ").append(mSatelliteCorrections);
@@ -140,6 +151,8 @@ public final class GlonassAssistance implements Parcelable {
return new GlonassAssistance.Builder()
.setAlmanac(in.readTypedObject(GlonassAlmanac.CREATOR))
.setUtcModel(in.readTypedObject(UtcModel.CREATOR))
+ .setAuxiliaryInformation(
+ in.readTypedObject(AuxiliaryInformation.CREATOR))
.setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
.setSatelliteEphemeris(
in.createTypedArrayList(GlonassSatelliteEphemeris.CREATOR))
@@ -158,30 +171,36 @@ public final class GlonassAssistance implements Parcelable {
public static final class Builder {
private GlonassAlmanac mAlmanac;
private UtcModel mUtcModel;
+ private AuxiliaryInformation mAuxiliaryInformation;
private List<TimeModel> mTimeModels;
private List<GlonassSatelliteEphemeris> mSatelliteEphemeris;
private List<GnssSatelliteCorrections> mSatelliteCorrections;
/** Sets the Glonass almanac. */
@NonNull
- public Builder setAlmanac(
- @Nullable @SuppressLint("NullableCollection") GlonassAlmanac almanac) {
+ public Builder setAlmanac(@Nullable GlonassAlmanac almanac) {
mAlmanac = almanac;
return this;
}
/** Sets the UTC model. */
@NonNull
- public Builder setUtcModel(
- @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) {
+ public Builder setUtcModel(@Nullable UtcModel utcModel) {
mUtcModel = utcModel;
return this;
}
+ /** Sets the auxiliary information. */
+ @NonNull
+ public Builder setAuxiliaryInformation(
+ @Nullable AuxiliaryInformation auxiliaryInformation) {
+ mAuxiliaryInformation = auxiliaryInformation;
+ return this;
+ }
+
/** Sets the list of time models. */
@NonNull
- public Builder setTimeModels(
- @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+ public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
mTimeModels = timeModels;
return this;
}
@@ -189,8 +208,7 @@ public final class GlonassAssistance implements Parcelable {
/** Sets the list of Glonass satellite ephemeris. */
@NonNull
public Builder setSatelliteEphemeris(
- @Nullable @SuppressLint("NullableCollection")
- List<GlonassSatelliteEphemeris> satelliteEphemeris) {
+ @NonNull List<GlonassSatelliteEphemeris> satelliteEphemeris) {
mSatelliteEphemeris = satelliteEphemeris;
return this;
}
@@ -198,8 +216,7 @@ public final class GlonassAssistance implements Parcelable {
/** Sets the list of Glonass satellite corrections. */
@NonNull
public Builder setSatelliteCorrections(
- @Nullable @SuppressLint("NullableCollection")
- List<GnssSatelliteCorrections> satelliteCorrections) {
+ @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
mSatelliteCorrections = satelliteCorrections;
return this;
}
diff --git a/location/java/android/location/GpsAssistance.java b/location/java/android/location/GpsAssistance.java
index 5202fc4cd851..5a8802f057e2 100644
--- a/location/java/android/location/GpsAssistance.java
+++ b/location/java/android/location/GpsAssistance.java
@@ -51,6 +51,9 @@ public final class GpsAssistance implements Parcelable {
/** The leap seconds model. */
@Nullable private final LeapSecondsModel mLeapSecondsModel;
+ /** The auxiliary information. */
+ @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
/** The list of time models. */
@NonNull private final List<TimeModel> mTimeModels;
@@ -68,6 +71,7 @@ public final class GpsAssistance implements Parcelable {
mIonosphericModel = builder.mIonosphericModel;
mUtcModel = builder.mUtcModel;
mLeapSecondsModel = builder.mLeapSecondsModel;
+ mAuxiliaryInformation = builder.mAuxiliaryInformation;
if (builder.mTimeModels != null) {
mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
} else {
@@ -117,6 +121,12 @@ public final class GpsAssistance implements Parcelable {
return mLeapSecondsModel;
}
+ /** Returns the auxiliary information. */
+ @Nullable
+ public AuxiliaryInformation getAuxiliaryInformation() {
+ return mAuxiliaryInformation;
+ }
+
/** Returns the list of time models. */
@NonNull
public List<TimeModel> getTimeModels() {
@@ -152,6 +162,8 @@ public final class GpsAssistance implements Parcelable {
in.readTypedObject(KlobucharIonosphericModel.CREATOR))
.setUtcModel(in.readTypedObject(UtcModel.CREATOR))
.setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+ .setAuxiliaryInformation(
+ in.readTypedObject(AuxiliaryInformation.CREATOR))
.setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
.setSatelliteEphemeris(
in.createTypedArrayList(GpsSatelliteEphemeris.CREATOR))
@@ -179,6 +191,7 @@ public final class GpsAssistance implements Parcelable {
dest.writeTypedObject(mIonosphericModel, flags);
dest.writeTypedObject(mUtcModel, flags);
dest.writeTypedObject(mLeapSecondsModel, flags);
+ dest.writeTypedObject(mAuxiliaryInformation, flags);
dest.writeTypedList(mTimeModels);
dest.writeTypedList(mSatelliteEphemeris);
dest.writeTypedList(mRealTimeIntegrityModels);
@@ -193,6 +206,7 @@ public final class GpsAssistance implements Parcelable {
builder.append(", ionosphericModel = ").append(mIonosphericModel);
builder.append(", utcModel = ").append(mUtcModel);
builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+ builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
builder.append(", timeModels = ").append(mTimeModels);
builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -207,6 +221,7 @@ public final class GpsAssistance implements Parcelable {
private KlobucharIonosphericModel mIonosphericModel;
private UtcModel mUtcModel;
private LeapSecondsModel mLeapSecondsModel;
+ private AuxiliaryInformation mAuxiliaryInformation;
private List<TimeModel> mTimeModels;
private List<GpsSatelliteEphemeris> mSatelliteEphemeris;
private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -222,33 +237,36 @@ public final class GpsAssistance implements Parcelable {
/** Sets the Klobuchar ionospheric model. */
@NonNull
- public Builder setIonosphericModel(
- @Nullable @SuppressLint("NullableCollection")
- KlobucharIonosphericModel ionosphericModel) {
+ public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) {
mIonosphericModel = ionosphericModel;
return this;
}
/** Sets the UTC model. */
@NonNull
- public Builder setUtcModel(
- @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) {
+ public Builder setUtcModel(@Nullable UtcModel utcModel) {
mUtcModel = utcModel;
return this;
}
/** Sets the leap seconds model. */
@NonNull
- public Builder setLeapSecondsModel(
- @Nullable @SuppressLint("NullableCollection") LeapSecondsModel leapSecondsModel) {
+ public Builder setLeapSecondsModel(@Nullable LeapSecondsModel leapSecondsModel) {
mLeapSecondsModel = leapSecondsModel;
return this;
}
+ /** Sets the auxiliary information. */
+ @NonNull
+ public Builder setAuxiliaryInformation(
+ @Nullable AuxiliaryInformation auxiliaryInformation) {
+ mAuxiliaryInformation = auxiliaryInformation;
+ return this;
+ }
+
/** Sets the list of time models. */
@NonNull
- public Builder setTimeModels(
- @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+ public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
mTimeModels = timeModels;
return this;
}
@@ -256,8 +274,7 @@ public final class GpsAssistance implements Parcelable {
/** Sets the list of GPS ephemeris. */
@NonNull
public Builder setSatelliteEphemeris(
- @Nullable @SuppressLint("NullableCollection")
- List<GpsSatelliteEphemeris> satelliteEphemeris) {
+ @NonNull List<GpsSatelliteEphemeris> satelliteEphemeris) {
mSatelliteEphemeris = satelliteEphemeris;
return this;
}
@@ -265,8 +282,7 @@ public final class GpsAssistance implements Parcelable {
/** Sets the list of real time integrity models. */
@NonNull
public Builder setRealTimeIntegrityModels(
- @Nullable @SuppressLint("NullableCollection")
- List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+ @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
mRealTimeIntegrityModels = realTimeIntegrityModels;
return this;
}
@@ -274,8 +290,7 @@ public final class GpsAssistance implements Parcelable {
/** Sets the list of GPS satellite corrections. */
@NonNull
public Builder setSatelliteCorrections(
- @Nullable @SuppressLint("NullableCollection")
- List<GnssSatelliteCorrections> satelliteCorrections) {
+ @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
mSatelliteCorrections = satelliteCorrections;
return this;
}
diff --git a/location/java/android/location/QzssAssistance.java b/location/java/android/location/QzssAssistance.java
index 9383ce3c63b5..27c34370316e 100644
--- a/location/java/android/location/QzssAssistance.java
+++ b/location/java/android/location/QzssAssistance.java
@@ -19,7 +19,6 @@ package android.location;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.location.GnssAssistance.GnssSatelliteCorrections;
import android.location.flags.Flags;
@@ -51,6 +50,9 @@ public final class QzssAssistance implements Parcelable {
/** The leap seconds model. */
@Nullable private final LeapSecondsModel mLeapSecondsModel;
+ /** The auxiliary information. */
+ @Nullable private final AuxiliaryInformation mAuxiliaryInformation;
+
/** The list of time models. */
@NonNull private final List<TimeModel> mTimeModels;
@@ -68,6 +70,7 @@ public final class QzssAssistance implements Parcelable {
mIonosphericModel = builder.mIonosphericModel;
mUtcModel = builder.mUtcModel;
mLeapSecondsModel = builder.mLeapSecondsModel;
+ mAuxiliaryInformation = builder.mAuxiliaryInformation;
if (builder.mTimeModels != null) {
mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels));
} else {
@@ -117,6 +120,12 @@ public final class QzssAssistance implements Parcelable {
return mLeapSecondsModel;
}
+ /** Returns the auxiliary information. */
+ @Nullable
+ public AuxiliaryInformation getAuxiliaryInformation() {
+ return mAuxiliaryInformation;
+ }
+
/** Returns the list of time models. */
@NonNull
public List<TimeModel> getTimeModels() {
@@ -147,19 +156,23 @@ public final class QzssAssistance implements Parcelable {
@NonNull
public QzssAssistance createFromParcel(Parcel in) {
return new QzssAssistance.Builder()
- .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR))
- .setIonosphericModel(in.readTypedObject(KlobucharIonosphericModel.CREATOR))
- .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
- .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
- .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
- .setSatelliteEphemeris(
- in.createTypedArrayList(QzssSatelliteEphemeris.CREATOR))
- .setRealTimeIntegrityModels(
- in.createTypedArrayList(RealTimeIntegrityModel.CREATOR))
- .setSatelliteCorrections(
- in.createTypedArrayList(GnssSatelliteCorrections.CREATOR))
- .build();
+ .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR))
+ .setIonosphericModel(
+ in.readTypedObject(KlobucharIonosphericModel.CREATOR))
+ .setUtcModel(in.readTypedObject(UtcModel.CREATOR))
+ .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
+ .setAuxiliaryInformation(
+ in.readTypedObject(AuxiliaryInformation.CREATOR))
+ .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR))
+ .setSatelliteEphemeris(
+ in.createTypedArrayList(QzssSatelliteEphemeris.CREATOR))
+ .setRealTimeIntegrityModels(
+ in.createTypedArrayList(RealTimeIntegrityModel.CREATOR))
+ .setSatelliteCorrections(
+ in.createTypedArrayList(GnssSatelliteCorrections.CREATOR))
+ .build();
}
+
@Override
public QzssAssistance[] newArray(int size) {
return new QzssAssistance[size];
@@ -177,6 +190,7 @@ public final class QzssAssistance implements Parcelable {
dest.writeTypedObject(mIonosphericModel, flags);
dest.writeTypedObject(mUtcModel, flags);
dest.writeTypedObject(mLeapSecondsModel, flags);
+ dest.writeTypedObject(mAuxiliaryInformation, flags);
dest.writeTypedList(mTimeModels);
dest.writeTypedList(mSatelliteEphemeris);
dest.writeTypedList(mRealTimeIntegrityModels);
@@ -191,6 +205,7 @@ public final class QzssAssistance implements Parcelable {
builder.append(", ionosphericModel = ").append(mIonosphericModel);
builder.append(", utcModel = ").append(mUtcModel);
builder.append(", leapSecondsModel = ").append(mLeapSecondsModel);
+ builder.append(", auxiliaryInformation = ").append(mAuxiliaryInformation);
builder.append(", timeModels = ").append(mTimeModels);
builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris);
builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels);
@@ -205,6 +220,7 @@ public final class QzssAssistance implements Parcelable {
private KlobucharIonosphericModel mIonosphericModel;
private UtcModel mUtcModel;
private LeapSecondsModel mLeapSecondsModel;
+ private AuxiliaryInformation mAuxiliaryInformation;
private List<TimeModel> mTimeModels;
private List<QzssSatelliteEphemeris> mSatelliteEphemeris;
private List<RealTimeIntegrityModel> mRealTimeIntegrityModels;
@@ -238,10 +254,17 @@ public final class QzssAssistance implements Parcelable {
return this;
}
+ /** Sets the auxiliary information. */
+ @NonNull
+ public Builder setAuxiliaryInformation(
+ @Nullable AuxiliaryInformation auxiliaryInformation) {
+ mAuxiliaryInformation = auxiliaryInformation;
+ return this;
+ }
+
/** Sets the list of time models. */
@NonNull
- public Builder setTimeModels(
- @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) {
+ public Builder setTimeModels(@NonNull List<TimeModel> timeModels) {
mTimeModels = timeModels;
return this;
}
@@ -249,8 +272,7 @@ public final class QzssAssistance implements Parcelable {
/** Sets the list of QZSS ephemeris. */
@NonNull
public Builder setSatelliteEphemeris(
- @Nullable @SuppressLint("NullableCollection")
- List<QzssSatelliteEphemeris> satelliteEphemeris) {
+ @NonNull List<QzssSatelliteEphemeris> satelliteEphemeris) {
mSatelliteEphemeris = satelliteEphemeris;
return this;
}
@@ -258,8 +280,7 @@ public final class QzssAssistance implements Parcelable {
/** Sets the list of real time integrity model. */
@NonNull
public Builder setRealTimeIntegrityModels(
- @Nullable @SuppressLint("NullableCollection")
- List<RealTimeIntegrityModel> realTimeIntegrityModels) {
+ @NonNull List<RealTimeIntegrityModel> realTimeIntegrityModels) {
mRealTimeIntegrityModels = realTimeIntegrityModels;
return this;
}
@@ -267,8 +288,7 @@ public final class QzssAssistance implements Parcelable {
/** Sets the list of QZSS satellite correction. */
@NonNull
public Builder setSatelliteCorrections(
- @Nullable @SuppressLint("NullableCollection")
- List<GnssSatelliteCorrections> satelliteCorrections) {
+ @NonNull List<GnssSatelliteCorrections> satelliteCorrections) {
mSatelliteCorrections = satelliteCorrections;
return this;
}
diff --git a/location/java/android/location/RealTimeIntegrityModel.java b/location/java/android/location/RealTimeIntegrityModel.java
index d268926e56e2..f065def35f7a 100644
--- a/location/java/android/location/RealTimeIntegrityModel.java
+++ b/location/java/android/location/RealTimeIntegrityModel.java
@@ -26,6 +26,10 @@ import android.os.Parcelable;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* A class contains the real time integrity status of a GNSS satellite based on notice advisory.
*
@@ -35,8 +39,7 @@ import com.android.internal.util.Preconditions;
@SystemApi
public final class RealTimeIntegrityModel implements Parcelable {
/**
- * Pseudo-random or satellite ID number for the satellite,
- * a.k.a. Space Vehicle (SV), or OSN number for Glonass.
+ * Bad satellite ID number or OSN number for Glonass.
*
* <p>The distinction is made by looking at the constellation field. Values
* must be in the range of:
@@ -47,10 +50,14 @@ public final class RealTimeIntegrityModel implements Parcelable {
* <p> - Galileo: 1-36
* <p> - Beidou: 1-63
*/
- private final int mSvid;
+ private final int mBadSvid;
- /** Indicates whether the satellite is currently usable for navigation. */
- private final boolean mUsable;
+ /**
+ * The type of the bad signal or signals.
+ *
+ * <p>An empty list means that all signals on the specific SV are not healthy.
+ */
+ @NonNull private final List<GnssSignalType> mBadSignalTypes;
/** UTC timestamp (in seconds) when the advisory was published. */
private final long mPublishDateSeconds;
@@ -81,14 +88,19 @@ public final class RealTimeIntegrityModel implements Parcelable {
private RealTimeIntegrityModel(Builder builder) {
// Allow SV ID beyond the range to support potential future extensibility.
- Preconditions.checkArgument(builder.mSvid >= 1);
+ Preconditions.checkArgument(builder.mBadSvid >= 1);
Preconditions.checkArgument(builder.mPublishDateSeconds > 0);
Preconditions.checkArgument(builder.mStartDateSeconds > 0);
Preconditions.checkArgument(builder.mEndDateSeconds > 0);
Preconditions.checkNotNull(builder.mAdvisoryType, "AdvisoryType cannot be null");
Preconditions.checkNotNull(builder.mAdvisoryNumber, "AdvisoryNumber cannot be null");
- mSvid = builder.mSvid;
- mUsable = builder.mUsable;
+ if (builder.mBadSignalTypes == null) {
+ mBadSignalTypes = new ArrayList<>();
+ } else {
+ mBadSignalTypes = Collections.unmodifiableList(
+ new ArrayList<>(builder.mBadSignalTypes));
+ }
+ mBadSvid = builder.mBadSvid;
mPublishDateSeconds = builder.mPublishDateSeconds;
mStartDateSeconds = builder.mStartDateSeconds;
mEndDateSeconds = builder.mEndDateSeconds;
@@ -110,13 +122,18 @@ public final class RealTimeIntegrityModel implements Parcelable {
* <p> - Beidou: 1-63
*/
@IntRange(from = 1, to = 206)
- public int getSvid() {
- return mSvid;
+ public int getBadSvid() {
+ return mBadSvid;
}
- /** Returns whether the satellite is usable or not. */
- public boolean isUsable() {
- return mUsable;
+ /**
+ * Returns the type of the bad signal or signals.
+ *
+ * <p>An empty list means that all signals on the specific SV are not healthy.
+ */
+ @NonNull
+ public List<GnssSignalType> getBadSignalTypes() {
+ return mBadSignalTypes;
}
/** Returns the UTC timestamp (in seconds) when the advisory was published */
@@ -156,8 +173,9 @@ public final class RealTimeIntegrityModel implements Parcelable {
public RealTimeIntegrityModel createFromParcel(Parcel in) {
RealTimeIntegrityModel realTimeIntegrityModel =
new RealTimeIntegrityModel.Builder()
- .setSvid(in.readInt())
- .setUsable(in.readBoolean())
+ .setBadSvid(in.readInt())
+ .setBadSignalTypes(
+ in.createTypedArrayList(GnssSignalType.CREATOR))
.setPublishDateSeconds(in.readLong())
.setStartDateSeconds(in.readLong())
.setEndDateSeconds(in.readLong())
@@ -180,8 +198,8 @@ public final class RealTimeIntegrityModel implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeInt(mSvid);
- parcel.writeBoolean(mUsable);
+ parcel.writeInt(mBadSvid);
+ parcel.writeTypedList(mBadSignalTypes);
parcel.writeLong(mPublishDateSeconds);
parcel.writeLong(mStartDateSeconds);
parcel.writeLong(mEndDateSeconds);
@@ -193,8 +211,8 @@ public final class RealTimeIntegrityModel implements Parcelable {
@NonNull
public String toString() {
StringBuilder builder = new StringBuilder("RealTimeIntegrityModel[");
- builder.append("svid = ").append(mSvid);
- builder.append(", usable = ").append(mUsable);
+ builder.append("badSvid = ").append(mBadSvid);
+ builder.append(", badSignalTypes = ").append(mBadSignalTypes);
builder.append(", publishDateSeconds = ").append(mPublishDateSeconds);
builder.append(", startDateSeconds = ").append(mStartDateSeconds);
builder.append(", endDateSeconds = ").append(mEndDateSeconds);
@@ -206,8 +224,8 @@ public final class RealTimeIntegrityModel implements Parcelable {
/** Builder for {@link RealTimeIntegrityModel} */
public static final class Builder {
- private int mSvid;
- private boolean mUsable;
+ private int mBadSvid;
+ private List<GnssSignalType> mBadSignalTypes;
private long mPublishDateSeconds;
private long mStartDateSeconds;
private long mEndDateSeconds;
@@ -215,8 +233,7 @@ public final class RealTimeIntegrityModel implements Parcelable {
private String mAdvisoryNumber;
/**
- * Sets the Pseudo-random or satellite ID number for the satellite,
- * a.k.a. Space Vehicle (SV), or OSN number for Glonass.
+ * Sets the bad satellite ID number or OSN number for Glonass.
*
* <p>The distinction is made by looking at the constellation field. Values
* must be in the range of:
@@ -228,15 +245,19 @@ public final class RealTimeIntegrityModel implements Parcelable {
* <p> - Beidou: 1-63
*/
@NonNull
- public Builder setSvid(@IntRange(from = 1, to = 206) int svid) {
- mSvid = svid;
+ public Builder setBadSvid(@IntRange(from = 1, to = 206) int badSvid) {
+ mBadSvid = badSvid;
return this;
}
- /** Sets whether the satellite is usable or not. */
+ /**
+ * Sets the type of the bad signal or signals.
+ *
+ * <p>An empty list means that all signals on the specific SV are not healthy.
+ */
@NonNull
- public Builder setUsable(boolean usable) {
- mUsable = usable;
+ public Builder setBadSignalTypes(@NonNull List<GnssSignalType> badSignalTypes) {
+ mBadSignalTypes = badSignalTypes;
return this;
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index c02cc808d60c..1b38982f48c1 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -167,4 +167,4 @@ flag {
namespace: "location"
description: "Flag for GNSS assistance interface"
bug: "209078566"
-} \ No newline at end of file
+}
diff --git a/location/java/android/location/provider/GnssAssistanceProviderBase.java b/location/java/android/location/provider/GnssAssistanceProviderBase.java
new file mode 100644
index 000000000000..f4b26d5033a5
--- /dev/null
+++ b/location/java/android/location/provider/GnssAssistanceProviderBase.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.location.GnssAssistance;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * Base class for GNSS assistance providers outside the system server.
+ *
+ * <p>GNSS assistance providers should be wrapped in a non-exported service which returns the result
+ * of {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The
+ * service should not be exported so that components other than the system server cannot bind to it.
+ * Alternatively, the service may be guarded by a permission that only system server can obtain. The
+ * service may specify metadata on its capabilities:
+ *
+ * <ul>
+ * <li>"serviceVersion": An integer version code to help tie break if multiple services are
+ * capable of implementing the geocode provider. All else equal, the service with the highest
+ * version code will be chosen. Assumed to be 0 if not specified.
+ * <li>"serviceIsMultiuser": A boolean property, indicating if the service wishes to take
+ * responsibility for handling changes to the current user on the device. If true, the service
+ * will always be bound from the system user. If false, the service will always be bound from
+ * the current user. If the current user changes, the old binding will be released, and a new
+ * binding established under the new user. Assumed to be false if not specified.
+ * </ul>
+ *
+ * <p>The service should have an intent filter in place for the GNSS assistance provider as
+ * specified by the constant in this class.
+ *
+ * <p>GNSS assistance providers are identified by their UID / package name / attribution tag. Based
+ * on this identity, geocode providers may be given some special privileges.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE)
+@SystemApi
+public abstract class GnssAssistanceProviderBase {
+
+ /**
+ * The action the wrapping service should have in its intent filter to implement the GNSS
+ * Assistance provider.
+ */
+ public static final String ACTION_GNSS_ASSISTANCE_PROVIDER =
+ "android.location.provider.action.GNSS_ASSISTANCE_PROVIDER";
+
+ final String mTag;
+ @Nullable
+ final String mAttributionTag;
+ final IBinder mBinder;
+
+ /**
+ * Subclasses should pass in a context and an arbitrary tag that may be used for logcat logging
+ * of errors, and thus should uniquely identify the class.
+ */
+ public GnssAssistanceProviderBase(@NonNull Context context, @NonNull String tag) {
+ mTag = tag;
+ mAttributionTag = context.getAttributionTag();
+ mBinder = new GnssAssistanceProviderBase.Service();
+ }
+
+ /**
+ * Returns the IBinder instance that should be returned from the {@link
+ * android.app.Service#onBind(Intent)} method of the wrapping service.
+ */
+ @NonNull
+ public final IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Requests GNSS assistance data of the given arguments. The given callback must be invoked
+ * once.
+ */
+ public abstract void onRequest(
+ @NonNull OutcomeReceiver<GnssAssistance, Throwable> callback);
+
+ private class Service extends IGnssAssistanceProvider.Stub {
+ @Override
+ public void request(IGnssAssistanceCallback callback) {
+ try {
+ onRequest(new GnssAssistanceProviderBase.SingleUseCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+ }
+
+ private static class SingleUseCallback implements
+ OutcomeReceiver<GnssAssistance, Throwable> {
+
+ private final AtomicReference<IGnssAssistanceCallback> mCallback;
+
+ SingleUseCallback(IGnssAssistanceCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onResult(GnssAssistance result) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(result);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/GraphicsTests.java b/location/java/android/location/provider/IGnssAssistanceCallback.aidl
index 70f5976843bc..ea38d08df6c2 100644
--- a/core/tests/coretests/src/android/graphics/GraphicsTests.java
+++ b/location/java/android/location/provider/IGnssAssistanceCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package android.graphics;
+package android.location.provider;
-import junit.framework.TestSuite;
+import android.location.GnssAssistance;
-public class GraphicsTests {
- public static TestSuite suite() {
- TestSuite suite = new TestSuite(GraphicsTests.class.getName());
-
- suite.addTestSuite(BitmapTest.class);
- return suite;
- }
+/**
+ * Binder interface for GNSS assistance callbacks.
+ * @hide
+ */
+oneway interface IGnssAssistanceCallback {
+ void onError();
+ void onResult(in GnssAssistance result);
}
diff --git a/location/java/android/location/provider/IGnssAssistanceProvider.aidl b/location/java/android/location/provider/IGnssAssistanceProvider.aidl
new file mode 100644
index 000000000000..1796e9edb347
--- /dev/null
+++ b/location/java/android/location/provider/IGnssAssistanceProvider.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.location.provider.IGnssAssistanceCallback;
+
+/**
+ * Binder interface for services that implement GNSS assistance providers. Do not implement this
+ * directly, extend {@link GnssAssistanceProviderBase} instead.
+ * @hide
+ */
+oneway interface IGnssAssistanceProvider {
+ void request(in IGnssAssistanceCallback callback);
+}
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 6d84e7066839..b91a5b57ac6b 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -673,6 +673,7 @@ public final class FadeManagerConfiguration implements Parcelable {
return config != null ? config.getDuration() : DURATION_NOT_SET;
}
+ @Nullable
private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
// if no volume shaper config is available, return null
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 302969f58ba8..19d39234d1c6 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -245,12 +245,7 @@ public final class MediaCodecInfo {
* {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
*/
public static final class CodecCapabilities {
- public CodecCapabilities() {
- }
-
- // CLASSIFICATION
- private String mMime;
- private int mMaxSupportedInstances;
+ private static final String TAG = "CodecCapabilities";
// LEGACY FIELDS
@@ -628,12 +623,6 @@ public final class MediaCodecInfo {
*/
public int[] colorFormats; // NOTE this array is modifiable by user
- // FEATURES
-
- private int mFlagsSupported;
- private int mFlagsRequired;
- private int mFlagsVerified;
-
/**
* <b>video decoder only</b>: codec supports seamless resolution changes.
*/
@@ -823,122 +812,680 @@ public final class MediaCodecInfo {
@FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
public static final String FEATURE_DetachedSurface = "detached-surface";
- /**
- * Query codec feature capabilities.
- * <p>
- * These features are supported to be used by the codec. These
- * include optional features that can be turned on, as well as
- * features that are always on.
- */
- public final boolean isFeatureSupported(String name) {
- return checkFeature(name, mFlagsSupported);
- }
+ /** package private */ interface CodecCapsIntf {
+ public CodecCapsIntf dup();
- /**
- * Query codec feature requirements.
- * <p>
- * These features are required to be used by the codec, and as such,
- * they are always turned on.
- */
- public final boolean isFeatureRequired(String name) {
- return checkFeature(name, mFlagsRequired);
+ public boolean isFeatureSupported(String name);
+
+ public boolean isFeatureRequired(String name);
+
+ public boolean isFormatSupported(MediaFormat format);
+
+ public MediaFormat getDefaultFormat();
+
+ public String getMimeType();
+
+ public int getMaxSupportedInstances();
+
+ public AudioCapabilities getAudioCapabilities();
+
+ public VideoCapabilities getVideoCapabilities();
+
+ public EncoderCapabilities getEncoderCapabilities();
+
+ public boolean isRegular();
+
+ public CodecProfileLevel[] getProfileLevels();
+
+ public int[] getColorFormats();
}
- // Flags are used for feature list creation so separate this into a private
- // static class to delay reading the flags only when constructing the list.
- private static class FeatureList {
- private static Feature[] getDecoderFeatures() {
- ArrayList<Feature> features = new ArrayList();
- features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
- features.add(new Feature(FEATURE_SecurePlayback, (1 << 1), false));
- features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
- features.add(new Feature(FEATURE_PartialFrame, (1 << 3), false));
- features.add(new Feature(FEATURE_FrameParsing, (1 << 4), false));
- features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false));
- features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
- features.add(new Feature(FEATURE_LowLatency, (1 << 7), true));
- if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
- features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
+ /* package private */ static final class CodecCapsLegacyImpl implements CodecCapsIntf {
+ // errors while reading profile levels - accessed from sister capabilities
+ int mError;
+
+ private CodecProfileLevel[] mProfileLevels;
+ private int[] mColorFormats;
+
+ // CLASSIFICATION
+ private String mMime;
+ private int mMaxSupportedInstances;
+
+ // FEATURES
+ private int mFlagsSupported;
+ private int mFlagsRequired;
+ private int mFlagsVerified;
+
+ // NEW-STYLE CAPABILITIES
+ private AudioCapabilities mAudioCaps;
+ private VideoCapabilities mVideoCaps;
+ private EncoderCapabilities mEncoderCaps;
+ private MediaFormat mDefaultFormat;
+
+ private MediaFormat mCapabilitiesInfo;
+
+ public CodecProfileLevel[] getProfileLevels() {
+ return mProfileLevels;
+ }
+
+ public int[] getColorFormats() {
+ return mColorFormats;
+ }
+
+ public CodecCapsLegacyImpl() {}
+
+ public CodecCapsLegacyImpl dup() {
+ CodecCapsLegacyImpl caps = new CodecCapsLegacyImpl();
+
+ caps.mProfileLevels = Arrays.copyOf(mProfileLevels, mProfileLevels.length);
+ caps.mColorFormats = Arrays.copyOf(mColorFormats, mColorFormats.length);
+
+ caps.mMime = mMime;
+ caps.mMaxSupportedInstances = mMaxSupportedInstances;
+ caps.mFlagsRequired = mFlagsRequired;
+ caps.mFlagsSupported = mFlagsSupported;
+ caps.mFlagsVerified = mFlagsVerified;
+ caps.mAudioCaps = mAudioCaps;
+ caps.mVideoCaps = mVideoCaps;
+ caps.mEncoderCaps = mEncoderCaps;
+ caps.mDefaultFormat = mDefaultFormat;
+ caps.mCapabilitiesInfo = mCapabilitiesInfo;
+
+ return caps;
+ }
+
+ public final boolean isFeatureSupported(String name) {
+ return checkFeature(name, mFlagsSupported);
+ }
+
+ public final boolean isFeatureRequired(String name) {
+ return checkFeature(name, mFlagsRequired);
+ }
+
+ // Flags are used for feature list creation so separate this into a private
+ // static class to delay reading the flags only when constructing the list.
+ private static class FeatureList {
+ private static Feature[] getDecoderFeatures() {
+ ArrayList<Feature> features = new ArrayList();
+ features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
+ features.add(new Feature(FEATURE_SecurePlayback, (1 << 1), false));
+ features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
+ features.add(new Feature(FEATURE_PartialFrame, (1 << 3), false));
+ features.add(new Feature(FEATURE_FrameParsing, (1 << 4), false));
+ features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false));
+ features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
+ features.add(new Feature(FEATURE_LowLatency, (1 << 7), true));
+ if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
+ features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
+ }
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
+ features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));
+ }
+
+ // feature to exclude codec from REGULAR codec list
+ features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+
+ return features.toArray(new Feature[0]);
+ };
+
+ private static Feature[] decoderFeatures = getDecoderFeatures();
+
+ private static Feature[] getEncoderFeatures() {
+ ArrayList<Feature> features = new ArrayList();
+
+ features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
+ features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
+ features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
+ features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
+ features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
+ features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
+ if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
+ features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
+ }
+ if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
+ features.add(new Feature(FEATURE_Roi, (1 << 7), true));
+ }
+
+ // feature to exclude codec from REGULAR codec list
+ features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+
+ return features.toArray(new Feature[0]);
+ };
+
+ private static Feature[] encoderFeatures = getEncoderFeatures();
+
+ public static Feature[] getFeatures(boolean isEncoder) {
+ if (isEncoder) {
+ return encoderFeatures;
+ } else {
+ return decoderFeatures;
+ }
}
- if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
- features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));
+ }
+
+ /** @hide */
+ public String[] validFeatures() {
+ Feature[] features = getValidFeatures();
+ String[] res = new String[features.length];
+ for (int i = 0; i < res.length; i++) {
+ if (!features[i].mInternal) {
+ res[i] = features[i].mName;
+ }
}
+ return res;
+ }
- // feature to exclude codec from REGULAR codec list
- features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+ private Feature[] getValidFeatures() {
+ return FeatureList.getFeatures(isEncoder());
+ }
- return features.toArray(new Feature[0]);
- };
+ private boolean checkFeature(String name, int flags) {
+ for (Feature feat: getValidFeatures()) {
+ if (feat.mName.equals(name)) {
+ return (flags & feat.mValue) != 0;
+ }
+ }
+ return false;
+ }
- private static Feature[] decoderFeatures = getDecoderFeatures();
+ public boolean isRegular() {
+ // regular codecs only require default features
+ for (Feature feat: getValidFeatures()) {
+ if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+ return false;
+ }
+ }
+ return true;
+ }
- private static Feature[] getEncoderFeatures() {
- ArrayList<Feature> features = new ArrayList();
+ public final boolean isFormatSupported(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = (String) map.get(MediaFormat.KEY_MIME);
- features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
- features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
- features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
- features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
- features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
- features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
- if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
- features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
+ // mime must match if present
+ if (mime != null && !mMime.equalsIgnoreCase(mime)) {
+ return false;
}
- if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
- features.add(new Feature(FEATURE_Roi, (1 << 7), true));
+
+ // check feature support
+ for (Feature feat: getValidFeatures()) {
+ if (feat.mInternal) {
+ continue;
+ }
+
+ Integer yesNo = (Integer) map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+ if (yesNo == null) {
+ continue;
+ }
+ if ((yesNo == 1 && !isFeatureSupported(feat.mName))
+ || (yesNo == 0 && isFeatureRequired(feat.mName))) {
+ return false;
+ }
}
- // feature to exclude codec from REGULAR codec list
- features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+ Integer profile = (Integer) map.get(MediaFormat.KEY_PROFILE);
+ Integer level = (Integer) map.get(MediaFormat.KEY_LEVEL);
- return features.toArray(new Feature[0]);
- };
+ if (profile != null) {
+ if (!supportsProfileLevel(profile, level)) {
+ return false;
+ }
- private static Feature[] encoderFeatures = getEncoderFeatures();
+ // If we recognize this profile, check that this format is supported by the
+ // highest level supported by the codec for that profile. (Ignore specified
+ // level beyond the above profile/level check as level is only used as a
+ // guidance. E.g. AVC Level 1 CIF format is supported if codec supports
+ // level 1.1 even though max size for Level 1 is QCIF. However, MPEG2 Simple
+ // Profile 1080p format is not supported even if codec supports Main Profile
+ // Level High, as Simple Profile does not support 1080p.
+ CodecCapsLegacyImpl levelCaps = null;
+ int maxLevel = 0;
+ for (CodecProfileLevel pl : mProfileLevels) {
+ if (pl.profile == profile && pl.level > maxLevel) {
+ // H.263 levels are not completely ordered:
+ // Level45 support only implies Level10 support
+ if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
+ || pl.level != CodecProfileLevel.H263Level45
+ || maxLevel == CodecProfileLevel.H263Level10) {
+ maxLevel = pl.level;
+ }
+ }
+ }
+ levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
+ // We must remove the profile from this format otherwise
+ // levelCaps.isFormatSupported will get into this same condition and loop
+ // forever. Furthermore, since levelCaps does not contain features and bitrate
+ // specific keys, keep only keys relevant for a level check.
+ Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
+ final Set<String> criticalKeys = isVideo()
+ ? VideoCapabilities.VideoCapsLegacyImpl.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS
+ : isAudio()
+ ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS
+ : null;
+
+ // critical keys will always contain KEY_MIME, but should also contain others
+ // to be meaningful
+ if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
+ levelCriticalFormatMap.keySet().retainAll(criticalKeys);
+
+ MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
+ if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
+ return false;
+ }
+ }
+ }
+ if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
+ return false;
+ }
+ return true;
+ }
- public static Feature[] getFeatures(boolean isEncoder) {
- if (isEncoder) {
- return encoderFeatures;
- } else {
- return decoderFeatures;
+ private static boolean supportsBitrate(
+ Range<Integer> bitrateRange, MediaFormat format) {
+ Map<String, Object> map = format.getMap();
+
+ // consider max bitrate over average bitrate for support
+ Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
+ Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
+ if (bitrate == null) {
+ bitrate = maxBitrate;
+ } else if (maxBitrate != null) {
+ bitrate = Math.max(bitrate, maxBitrate);
+ }
+
+ if (bitrate != null && bitrate > 0) {
+ return bitrateRange.contains(bitrate);
}
+
+ return true;
}
- }
- /** @hide */
- public String[] validFeatures() {
- Feature[] features = getValidFeatures();
- String[] res = new String[features.length];
- for (int i = 0; i < res.length; i++) {
- if (!features[i].mInternal) {
- res[i] = features[i].mName;
+ private boolean supportsProfileLevel(int profile, Integer level) {
+ for (CodecProfileLevel pl: mProfileLevels) {
+ if (pl.profile != profile) {
+ continue;
+ }
+
+ // No specific level requested
+ if (level == null) {
+ return true;
+ }
+
+ // AAC doesn't use levels
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ return true;
+ }
+
+ // DTS doesn't use levels
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
+ || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
+ || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+ return true;
+ }
+
+ // H.263 levels are not completely ordered:
+ // Level45 support only implies Level10 support
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+ if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
+ && level > CodecProfileLevel.H263Level10) {
+ continue;
+ }
+ }
+
+ // MPEG4 levels are not completely ordered:
+ // Level1 support only implies Level0 (and not Level0b) support
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
+ && level > CodecProfileLevel.MPEG4Level0) {
+ continue;
+ }
+ }
+
+ // HEVC levels incorporate both tiers and levels. Verify tier support.
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ boolean supportsHighTier =
+ (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+ boolean checkingHighTier
+ = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+ // high tier levels are only supported by other high tier levels
+ if (checkingHighTier && !supportsHighTier) {
+ continue;
+ }
+ }
+
+ if (pl.level >= level) {
+ // if we recognize the listed profile/level, we must also recognize the
+ // profile/level arguments.
+ if (createFromProfileLevel(mMime, profile, pl.level) != null) {
+ return createFromProfileLevel(mMime, profile, level) != null;
+ }
+ return true;
+ }
}
+ return false;
+ }
+
+ public MediaFormat getDefaultFormat() {
+ return mDefaultFormat;
+ }
+
+ public String getMimeType() {
+ return mMime;
+ }
+
+ public int getMaxSupportedInstances() {
+ return mMaxSupportedInstances;
}
- return res;
- }
- private Feature[] getValidFeatures() {
- return FeatureList.getFeatures(isEncoder());
+ private boolean isAudio() {
+ return mAudioCaps != null;
+ }
+
+ public AudioCapabilities getAudioCapabilities() {
+ return mAudioCaps;
+ }
+
+ private boolean isEncoder() {
+ return mEncoderCaps != null;
+ }
+
+ public EncoderCapabilities getEncoderCapabilities() {
+ return mEncoderCaps;
+ }
+
+ private boolean isVideo() {
+ return mVideoCaps != null;
+ }
+
+ public VideoCapabilities getVideoCapabilities() {
+ return mVideoCaps;
+ }
+
+ public static CodecCapsLegacyImpl createFromProfileLevel(
+ String mime, int profile, int level) {
+ CodecProfileLevel pl = new CodecProfileLevel();
+ pl.profile = profile;
+ pl.level = level;
+ MediaFormat defaultFormat = new MediaFormat();
+ defaultFormat.setString(MediaFormat.KEY_MIME, mime);
+
+ CodecCapsLegacyImpl ret = new CodecCapsLegacyImpl(
+ new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
+ defaultFormat, new MediaFormat() /* info */);
+ if (ret.mError != 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /* package private */ CodecCapsLegacyImpl(
+ CodecProfileLevel[] profLevs, int[] colFmts,
+ boolean encoder,
+ Map<String, Object>defaultFormatMap,
+ Map<String, Object>capabilitiesMap) {
+ this(profLevs, colFmts, encoder,
+ new MediaFormat(defaultFormatMap),
+ new MediaFormat(capabilitiesMap));
+ }
+
+ /* package private */ CodecCapsLegacyImpl(
+ CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
+ MediaFormat defaultFormat, MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ mColorFormats = colFmts;
+ mFlagsVerified = 0; // TODO: remove as it is unused
+ mDefaultFormat = defaultFormat;
+ mCapabilitiesInfo = info;
+ mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
+
+ /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
+ supported profiles. Determine the level for them using the info they provide. */
+ if (profLevs.length == 0
+ && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ CodecProfileLevel profLev = new CodecProfileLevel();
+ profLev.profile = CodecProfileLevel.VP9Profile0;
+ profLev.level = VideoCapabilities.VideoCapsLegacyImpl.equivalentVP9Level(info);
+ profLevs = new CodecProfileLevel[] { profLev };
+ }
+ mProfileLevels = profLevs;
+
+ if (mMime.toLowerCase().startsWith("audio/")) {
+ mAudioCaps = AudioCapabilities.create(info, this);
+ mAudioCaps.getDefaultFormat(mDefaultFormat);
+ } else if (mMime.toLowerCase().startsWith("video/")
+ || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
+ mVideoCaps = VideoCapabilities.create(info, this);
+ }
+ if (encoder) {
+ mEncoderCaps = EncoderCapabilities.create(info, this);
+ mEncoderCaps.getDefaultFormat(mDefaultFormat);
+ }
+
+ final Map<String, Object> global = MediaCodecList.getGlobalSettings();
+ mMaxSupportedInstances = Utils.parseIntSafely(
+ global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
+
+ int maxInstances = Utils.parseIntSafely(
+ map.get("max-concurrent-instances"), mMaxSupportedInstances);
+ mMaxSupportedInstances =
+ Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
+
+ for (Feature feat: getValidFeatures()) {
+ String key = MediaFormat.KEY_FEATURE_ + feat.mName;
+ Integer yesNo = (Integer)map.get(key);
+ if (yesNo == null) {
+ continue;
+ }
+ if (yesNo > 0) {
+ mFlagsRequired |= feat.mValue;
+ }
+ mFlagsSupported |= feat.mValue;
+ if (!feat.mInternal) {
+ mDefaultFormat.setInteger(key, 1);
+ }
+ // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+ }
+ }
}
- private boolean checkFeature(String name, int flags) {
- for (Feature feat: getValidFeatures()) {
- if (feat.mName.equals(name)) {
- return (flags & feat.mValue) != 0;
+ /* package private */ static final class CodecCapsNativeImpl implements CodecCapsIntf {
+ private long mNativeContext; // accessed by native methods
+
+ private CodecProfileLevel[] mProfileLevels;
+ private int[] mColorFormats;
+
+ private MediaFormat mDefaultFormat;
+ private AudioCapabilities mAudioCaps;
+ private VideoCapabilities mVideoCaps;
+ private EncoderCapabilities mEncoderCaps;
+
+ public static CodecCapsNativeImpl createFromProfileLevel(
+ String mime, int profile, int level) {
+ return native_createFromProfileLevel(mime, profile, level);
+ }
+
+ /**
+ * Constructor used by JNI.
+ *
+ * The Java CodecCapabilities object keeps these subobjects to avoid recontructing.
+ */
+ /* package private */ CodecCapsNativeImpl(CodecProfileLevel[] profLevs, int[] colFmts,
+ MediaFormat defaultFormat, AudioCapabilities audioCaps,
+ VideoCapabilities videoCaps, EncoderCapabilities encoderCaps) {
+ mProfileLevels = profLevs;
+ mColorFormats = colFmts;
+ mDefaultFormat = defaultFormat;
+ mAudioCaps = audioCaps;
+ mVideoCaps = videoCaps;
+ mEncoderCaps = encoderCaps;
+ }
+
+ public CodecCapsNativeImpl dup() {
+ CodecCapsNativeImpl impl = native_dup();
+ return impl;
+ }
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ public CodecProfileLevel[] getProfileLevels() {
+ return mProfileLevels;
+ }
+
+ public int[] getColorFormats() {
+ return mColorFormats;
+ }
+
+ public final boolean isFeatureSupported(String name) {
+ return native_isFeatureSupported(name);
+ }
+
+ public final boolean isFeatureRequired(String name) {
+ return native_isFeatureRequired(name);
+ }
+
+ public boolean isRegular() {
+ return native_isRegular();
+ }
+
+ public final boolean isFormatSupported(MediaFormat format) {
+ if (format == null) {
+ throw new NullPointerException();
+ }
+
+ Map<String, Object> formatMap = format.getMap();
+ String[] keys = new String[formatMap.size()];
+ Object[] values = new Object[formatMap.size()];
+
+ int i = 0;
+ for (Map.Entry<String, Object> entry: formatMap.entrySet()) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ ++i;
}
+
+ return native_isFormatSupported(keys, values);
}
- return false;
+
+ public MediaFormat getDefaultFormat() {
+ return mDefaultFormat;
+ }
+
+ public String getMimeType() {
+ return native_getMimeType();
+ }
+
+ public int getMaxSupportedInstances() {
+ return native_getMaxSupportedInstances();
+ }
+
+ public AudioCapabilities getAudioCapabilities() {
+ return mAudioCaps;
+ }
+
+ public EncoderCapabilities getEncoderCapabilities() {
+ return mEncoderCaps;
+ }
+
+ public VideoCapabilities getVideoCapabilities() {
+ return mVideoCaps;
+ }
+
+ private static native void native_init();
+ private static native CodecCapsNativeImpl native_createFromProfileLevel(
+ String mime, int profile, int level);
+ private native CodecCapsNativeImpl native_dup();
+ private native void native_finalize();
+ private native int native_getMaxSupportedInstances();
+ private native String native_getMimeType();
+ private native final boolean native_isFeatureRequired(String name);
+ private native final boolean native_isFeatureSupported(String name);
+ private native final boolean native_isFormatSupported(@Nullable String[] keys,
+ @Nullable Object[] values);
+ private native boolean native_isRegular();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+ }
+
+ private CodecCapsIntf mImpl;
+
+ /**
+ * Retrieve the codec capabilities for a certain {@code mime type}, {@code
+ * profile} and {@code level}. If the type, or profile-level combination
+ * is not understood by the framework, it returns null.
+ * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
+ * method without calling any method of the {@link MediaCodecList} class beforehand
+ * results in a {@link NullPointerException}.</p>
+ */
+ public static CodecCapabilities createFromProfileLevel(
+ String mime, int profile, int level) {
+ CodecCapsIntf impl;
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ impl = CodecCapsNativeImpl.createFromProfileLevel(mime, profile, level);
+ } else {
+ impl = CodecCapsLegacyImpl.createFromProfileLevel(mime, profile, level);
+ }
+ return new CodecCapabilities(impl);
+ }
+
+ public CodecCapabilities() {
+ mImpl = new CodecCapsLegacyImpl();
+ }
+
+ /** package private */ CodecCapabilities(CodecCapsIntf impl) {
+ mImpl = impl;
+ profileLevels = mImpl.getProfileLevels();
+ colorFormats = mImpl.getColorFormats();
+ }
+
+ /** @hide */
+ public CodecCapabilities dup() {
+ CodecCapabilities caps = new CodecCapabilities();
+
+ // profileLevels and colorFormats may be modified by client.
+ caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
+ caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
+
+ caps.mImpl = mImpl.dup();
+
+ return caps;
+ }
+
+ /**
+ * Query codec feature capabilities.
+ * <p>
+ * These features are supported to be used by the codec. These
+ * include optional features that can be turned on, as well as
+ * features that are always on.
+ */
+ public final boolean isFeatureSupported(String name) {
+ return mImpl.isFeatureSupported(name);
+ }
+
+ /**
+ * Query codec feature requirements.
+ * <p>
+ * These features are required to be used by the codec, and as such,
+ * they are always turned on.
+ */
+ public final boolean isFeatureRequired(String name) {
+ return mImpl.isFeatureRequired(name);
}
/** @hide */
public boolean isRegular() {
- // regular codecs only require default features
- for (Feature feat: getValidFeatures()) {
- if (!feat.mDefault && isFeatureRequired(feat.mName)) {
- return false;
- }
- }
- return true;
+ return mImpl.isRegular();
}
/**
@@ -1047,384 +1594,573 @@ public final class MediaCodecInfo {
* and feature requests.
*/
public final boolean isFormatSupported(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = (String)map.get(MediaFormat.KEY_MIME);
+ return mImpl.isFormatSupported(format);
+ }
- // mime must match if present
- if (mime != null && !mMime.equalsIgnoreCase(mime)) {
- return false;
+ /**
+ * Returns a MediaFormat object with default values for configurations that have
+ * defaults.
+ */
+ public MediaFormat getDefaultFormat() {
+ return mImpl.getDefaultFormat();
+ }
+
+ /**
+ * Returns the mime type for which this codec-capability object was created.
+ */
+ public String getMimeType() {
+ return mImpl.getMimeType();
+ }
+
+ /**
+ * Returns the max number of the supported concurrent codec instances.
+ * <p>
+ * This is a hint for an upper bound. Applications should not expect to successfully
+ * operate more instances than the returned value, but the actual number of
+ * concurrently operable instances may be less as it depends on the available
+ * resources at time of use.
+ */
+ public int getMaxSupportedInstances() {
+ return mImpl.getMaxSupportedInstances();
+ }
+
+ /**
+ * Returns the audio capabilities or {@code null} if this is not an audio codec.
+ */
+ public AudioCapabilities getAudioCapabilities() {
+ return mImpl.getAudioCapabilities();
+ }
+
+ /**
+ * Returns the encoding capabilities or {@code null} if this is not an encoder.
+ */
+ public EncoderCapabilities getEncoderCapabilities() {
+ return mImpl.getEncoderCapabilities();
+ }
+
+ /**
+ * Returns the video capabilities or {@code null} if this is not a video codec.
+ */
+ public VideoCapabilities getVideoCapabilities() {
+ return mImpl.getVideoCapabilities();
+ }
+ }
+
+ /**
+ * A class that supports querying the audio capabilities of a codec.
+ */
+ public static final class AudioCapabilities {
+ private static final String TAG = "AudioCapabilities";
+
+ /* package private */ interface AudioCapsIntf {
+ public Range<Integer> getBitrateRange();
+
+ public int[] getSupportedSampleRates();
+
+ public Range<Integer>[] getSupportedSampleRateRanges();
+
+ public int getMaxInputChannelCount();
+
+ public int getMinInputChannelCount();
+
+ public Range<Integer>[] getInputChannelCountRanges();
+
+ public boolean isSampleRateSupported(int sampleRate);
+
+ public void getDefaultFormat(MediaFormat format);
+
+ public boolean supportsFormat(MediaFormat format);
+ }
+
+ /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf {
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
+ private Range<Integer> mBitrateRange;
+
+ private int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private Range<Integer>[] mInputChannelRanges;
+
+ private static final int MAX_INPUT_CHANNEL_COUNT = 30;
+
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
}
- // check feature support
- for (Feature feat: getValidFeatures()) {
- if (feat.mInternal) {
- continue;
- }
+ public int[] getSupportedSampleRates() {
+ return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
+ : null;
+ }
+
+ public Range<Integer>[] getSupportedSampleRateRanges() {
+ return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+ }
- Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
- if (yesNo == null) {
- continue;
+ public int getMaxInputChannelCount() {
+ int overall_max = 0;
+ for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+ int lmax = mInputChannelRanges[i].getUpper();
+ if (lmax > overall_max) {
+ overall_max = lmax;
+ }
}
- if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
- (yesNo == 0 && isFeatureRequired(feat.mName))) {
- return false;
+ return overall_max;
+ }
+
+ public int getMinInputChannelCount() {
+ int overall_min = MAX_INPUT_CHANNEL_COUNT;
+ for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+ int lmin = mInputChannelRanges[i].getLower();
+ if (lmin < overall_min) {
+ overall_min = lmin;
+ }
}
+ return overall_min;
}
- Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
- Integer level = (Integer)map.get(MediaFormat.KEY_LEVEL);
+ public Range<Integer>[] getInputChannelCountRanges() {
+ return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
+ }
- if (profile != null) {
- if (!supportsProfileLevel(profile, level)) {
- return false;
+ /* no public constructor */
+ private AudioCapsLegacyImpl() { }
+
+ public static AudioCapsLegacyImpl create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ Log.d(TAG, "Legacy implementation is called while native flag is on.");
}
- // If we recognize this profile, check that this format is supported by the
- // highest level supported by the codec for that profile. (Ignore specified
- // level beyond the above profile/level check as level is only used as a
- // guidance. E.g. AVC Level 1 CIF format is supported if codec supports level 1.1
- // even though max size for Level 1 is QCIF. However, MPEG2 Simple Profile
- // 1080p format is not supported even if codec supports Main Profile Level High,
- // as Simple Profile does not support 1080p.
- CodecCapabilities levelCaps = null;
- int maxLevel = 0;
- for (CodecProfileLevel pl : profileLevels) {
- if (pl.profile == profile && pl.level > maxLevel) {
- // H.263 levels are not completely ordered:
- // Level45 support only implies Level10 support
- if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
- || pl.level != CodecProfileLevel.H263Level45
- || maxLevel == CodecProfileLevel.H263Level10) {
- maxLevel = pl.level;
- }
+ AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void initWithPlatformLimits() {
+ mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+ mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
+ // mBitrateRange = Range.create(1, 320000);
+ final int minSampleRate = SystemProperties.
+ getInt("ro.mediacodec.min_sample_rate", 7350);
+ final int maxSampleRate = SystemProperties.
+ getInt("ro.mediacodec.max_sample_rate", 192000);
+ mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
+ mSampleRates = null;
+ }
+
+ private boolean supports(Integer sampleRate, Integer inputChannels) {
+ // channels and sample rates are checked orthogonally
+ if (inputChannels != null) {
+ int ix = Utils.binarySearchDistinctRanges(
+ mInputChannelRanges, inputChannels);
+ if (ix < 0) {
+ return false;
}
}
- levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
- // We must remove the profile from this format otherwise levelCaps.isFormatSupported
- // will get into this same condition and loop forever. Furthermore, since levelCaps
- // does not contain features and bitrate specific keys, keep only keys relevant for
- // a level check.
- Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
- final Set<String> criticalKeys =
- isVideo() ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS :
- isAudio() ? AudioCapabilities.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS :
- null;
-
- // critical keys will always contain KEY_MIME, but should also contain others to be
- // meaningful
- if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
- levelCriticalFormatMap.keySet().retainAll(criticalKeys);
-
- MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
- if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
+ if (sampleRate != null) {
+ int ix = Utils.binarySearchDistinctRanges(
+ mSampleRateRanges, sampleRate);
+ if (ix < 0) {
return false;
}
}
+ return true;
}
- if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
- return false;
- }
- if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
- return false;
- }
- if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
- return false;
- }
- return true;
- }
- private static boolean supportsBitrate(
- Range<Integer> bitrateRange, MediaFormat format) {
- Map<String, Object> map = format.getMap();
+ public boolean isSampleRateSupported(int sampleRate) {
+ return supports(sampleRate, null);
+ }
- // consider max bitrate over average bitrate for support
- Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
- Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
- if (bitrate == null) {
- bitrate = maxBitrate;
- } else if (maxBitrate != null) {
- bitrate = Math.max(bitrate, maxBitrate);
+ /** modifies rates */
+ private void limitSampleRates(int[] rates) {
+ Arrays.sort(rates);
+ ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
+ for (int rate: rates) {
+ if (supports(rate, null /* channels */)) {
+ ranges.add(Range.create(rate, rate));
+ }
+ }
+ mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
+ createDiscreteSampleRates();
}
- if (bitrate != null && bitrate > 0) {
- return bitrateRange.contains(bitrate);
+ private void createDiscreteSampleRates() {
+ mSampleRates = new int[mSampleRateRanges.length];
+ for (int i = 0; i < mSampleRateRanges.length; i++) {
+ mSampleRates[i] = mSampleRateRanges[i].getLower();
+ }
}
- return true;
- }
+ /** modifies rateRanges */
+ private void limitSampleRates(Range<Integer>[] rateRanges) {
+ sortDistinctRanges(rateRanges);
+ mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
- private boolean supportsProfileLevel(int profile, Integer level) {
- for (CodecProfileLevel pl: profileLevels) {
- if (pl.profile != profile) {
- continue;
+ // check if all values are discrete
+ for (Range<Integer> range: mSampleRateRanges) {
+ if (!range.getLower().equals(range.getUpper())) {
+ mSampleRates = null;
+ return;
+ }
}
+ createDiscreteSampleRates();
+ }
- // No specific level requested
- if (level == null) {
- return true;
+ private void applyLevelLimits() {
+ int[] sampleRates = null;
+ Range<Integer> sampleRateRange = null, bitRates = null;
+ int maxChannels = MAX_INPUT_CHANNEL_COUNT;
+ CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
+ String mime = mParent.getMimeType();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+ sampleRates = new int[] {
+ 8000, 11025, 12000,
+ 16000, 22050, 24000,
+ 32000, 44100, 48000 };
+ bitRates = Range.create(8000, 320000);
+ maxChannels = 2;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(4750, 12200);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+ sampleRates = new int[] { 16000 };
+ bitRates = Range.create(6600, 23850);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ sampleRates = new int[] {
+ 7350, 8000,
+ 11025, 12000, 16000,
+ 22050, 24000, 32000,
+ 44100, 48000, 64000,
+ 88200, 96000 };
+ bitRates = Range.create(8000, 510000);
+ maxChannels = 48;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
+ bitRates = Range.create(32000, 500000);
+ sampleRateRange = Range.create(8000, 192000);
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
+ bitRates = Range.create(6000, 510000);
+ sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
+ sampleRateRange = Range.create(1, 192000);
+ bitRates = Range.create(1, 10000000);
+ maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ sampleRateRange = Range.create(1, 655350);
+ // lossless codec, so bitrate is ignored
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(64000, 64000);
+ // platform allows multiple channels for this format
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(13000, 13000);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
+ maxChannels = 6;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
+ maxChannels = 16;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
+ sampleRates = new int[] { 48000 };
+ bitRates = Range.create(32000, 6144000);
+ maxChannels = 16;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
+ sampleRates = new int[] { 44100, 48000, 96000, 192000 };
+ bitRates = Range.create(16000, 2688000);
+ maxChannels = 24;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
+ sampleRates = new int[] { 44100, 48000 };
+ bitRates = Range.create(96000, 1524000);
+ maxChannels = 6;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.DTS_HDProfileLBR:
+ sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
+ bitRates = Range.create(32000, 768000);
+ break;
+ case CodecProfileLevel.DTS_HDProfileHRA:
+ case CodecProfileLevel.DTS_HDProfileMA:
+ sampleRates
+ = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+ bitRates = Range.create(96000, 24500000);
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ mParent.mError |= ERROR_UNRECOGNIZED;
+ sampleRates
+ = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+ bitRates = Range.create(96000, 24500000);
+ }
+ }
+ maxChannels = 8;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.DTS_UHDProfileP2:
+ sampleRates = new int[]{ 48000 };
+ bitRates = Range.create(96000, 768000);
+ maxChannels = 10;
+ break;
+ case CodecProfileLevel.DTS_UHDProfileP1:
+ sampleRates
+ = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+ bitRates = Range.create(96000, 24500000);
+ maxChannels = 32;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ mParent.mError |= ERROR_UNRECOGNIZED;
+ sampleRates
+ = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+ bitRates = Range.create(96000, 24500000);
+ maxChannels = 32;
+ }
+ }
+ } else {
+ Log.w(TAG, "Unsupported mime " + mime);
+ mParent.mError |= ERROR_UNSUPPORTED;
}
- // AAC doesn't use levels
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
- return true;
+ // restrict ranges
+ if (sampleRates != null) {
+ limitSampleRates(sampleRates);
+ } else if (sampleRateRange != null) {
+ limitSampleRates(new Range[] { sampleRateRange });
}
- // DTS doesn't use levels
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
- || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
- || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
- return true;
+ Range<Integer> channelRange = Range.create(1, maxChannels);
+
+ applyLimits(new Range[] { channelRange }, bitRates);
+ }
+
+ private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+ // clamp & make a local copy
+ Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+ for (int i = 0; i < inputChannels.length; i++) {
+ int lower = inputChannels[i].clamp(1);
+ int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+ myInputChannels[i] = Range.create(lower, upper);
}
- // H.263 levels are not completely ordered:
- // Level45 support only implies Level10 support
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
- if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
- && level > CodecProfileLevel.H263Level10) {
- continue;
- }
+ // sort, intersect with existing, & save channel list
+ sortDistinctRanges(myInputChannels);
+ Range<Integer>[] joinedChannelList =
+ intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+ mInputChannelRanges = joinedChannelList;
+
+ if (bitRates != null) {
+ mBitrateRange = mBitrateRange.intersect(bitRates);
}
+ }
- // MPEG4 levels are not completely ordered:
- // Level1 support only implies Level0 (and not Level0b) support
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
- && level > CodecProfileLevel.MPEG4Level0) {
- continue;
+ private void parseFromInfo(MediaFormat info) {
+ int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+ Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
+ Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+ if (info.containsKey("sample-rate-ranges")) {
+ String[] rateStrings = info.getString("sample-rate-ranges").split(",");
+ Range<Integer>[] rateRanges = new Range[rateStrings.length];
+ for (int i = 0; i < rateStrings.length; i++) {
+ rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
}
+ limitSampleRates(rateRanges);
}
- // HEVC levels incorporate both tiers and levels. Verify tier support.
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
- boolean supportsHighTier =
- (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
- boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
- // high tier levels are only supported by other high tier levels
- if (checkingHighTier && !supportsHighTier) {
- continue;
+ // we will prefer channel-ranges over max-channel-count
+ if (info.containsKey("channel-ranges")) {
+ String[] channelStrings = info.getString("channel-ranges").split(",");
+ Range<Integer>[] channelRanges = new Range[channelStrings.length];
+ for (int i = 0; i < channelStrings.length; i++) {
+ channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+ }
+ channels = channelRanges;
+ } else if (info.containsKey("channel-range")) {
+ Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+ null);
+ channels = new Range[] { oneRange };
+ } else if (info.containsKey("max-channel-count")) {
+ maxInputChannels = Utils.parseIntSafely(
+ info.getString("max-channel-count"), maxInputChannels);
+ if (maxInputChannels == 0) {
+ channels = new Range[] {Range.create(0, 0)};
+ } else {
+ channels = new Range[] {Range.create(1, maxInputChannels)};
}
+ } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+ maxInputChannels = 0;
+ channels = new Range[] {Range.create(0, 0)};
}
- if (pl.level >= level) {
- // if we recognize the listed profile/level, we must also recognize the
- // profile/level arguments.
- if (createFromProfileLevel(mMime, profile, pl.level) != null) {
- return createFromProfileLevel(mMime, profile, level) != null;
- }
- return true;
+ if (info.containsKey("bitrate-range")) {
+ bitRates = bitRates.intersect(
+ Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
}
+
+ applyLimits(channels, bitRates);
}
- return false;
- }
- // errors while reading profile levels - accessed from sister capabilities
- int mError;
+ /** @hide */
+ public void getDefaultFormat(MediaFormat format) {
+ // report settings that have only a single choice
+ if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
+ format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
+ }
+ if (getMaxInputChannelCount() == 1) {
+ // mono-only format
+ format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+ }
+ if (mSampleRates != null && mSampleRates.length == 1) {
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
+ }
+ }
- private static final String TAG = "CodecCapabilities";
+ /* package private */
+ // must not contain KEY_PROFILE
+ static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+ // We don't set level-specific limits for audio codecs today. Key candidates
+ // would be sample rate, bit rate or channel count.
+ // MediaFormat.KEY_SAMPLE_RATE,
+ // MediaFormat.KEY_CHANNEL_COUNT,
+ // MediaFormat.KEY_BIT_RATE,
+ MediaFormat.KEY_MIME);
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ Map<String, Object> map = format.getMap();
+ Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
+ Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
+
+ if (!supports(sampleRate, channels)) {
+ return false;
+ }
- // NEW-STYLE CAPABILITIES
- private AudioCapabilities mAudioCaps;
- private VideoCapabilities mVideoCaps;
- private EncoderCapabilities mEncoderCaps;
- private MediaFormat mDefaultFormat;
+ if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
+ return false;
+ }
- /**
- * Returns a MediaFormat object with default values for configurations that have
- * defaults.
- */
- public MediaFormat getDefaultFormat() {
- return mDefaultFormat;
+ // nothing to do for:
+ // KEY_CHANNEL_MASK: codecs don't get this
+ // KEY_IS_ADTS: required feature for all AAC decoders
+ return true;
+ }
}
- /**
- * Returns the mime type for which this codec-capability object was created.
- */
- public String getMimeType() {
- return mMime;
- }
+ /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf {
+ private long mNativeContext; // accessed by native methods
- /**
- * Returns the max number of the supported concurrent codec instances.
- * <p>
- * This is a hint for an upper bound. Applications should not expect to successfully
- * operate more instances than the returned value, but the actual number of
- * concurrently operable instances may be less as it depends on the available
- * resources at time of use.
- */
- public int getMaxSupportedInstances() {
- return mMaxSupportedInstances;
- }
+ private Range<Integer> mBitrateRange;
+ private int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private Range<Integer>[] mInputChannelRanges;
- private boolean isAudio() {
- return mAudioCaps != null;
- }
+ /**
+ * Constructor used by JNI.
+ *
+ * The Java AudioCapabilities object keeps these subobjects to avoid recontruction.
+ */
+ /* package private */ AudioCapsNativeImpl(Range<Integer> bitrateRange,
+ int[] sampleRates, Range<Integer>[] sampleRateRanges,
+ Range<Integer>[] inputChannelRanges) {
+ mBitrateRange = bitrateRange;
+ mSampleRates = sampleRates;
+ mSampleRateRanges = sampleRateRanges;
+ mInputChannelRanges = inputChannelRanges;
+ }
- /**
- * Returns the audio capabilities or {@code null} if this is not an audio codec.
- */
- public AudioCapabilities getAudioCapabilities() {
- return mAudioCaps;
- }
+ /* no public constructor */
+ private AudioCapsNativeImpl() { }
- private boolean isEncoder() {
- return mEncoderCaps != null;
- }
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
- /**
- * Returns the encoding capabilities or {@code null} if this is not an encoder.
- */
- public EncoderCapabilities getEncoderCapabilities() {
- return mEncoderCaps;
- }
+ public int[] getSupportedSampleRates() {
+ return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
+ : null;
+ }
- private boolean isVideo() {
- return mVideoCaps != null;
- }
+ public Range<Integer>[] getSupportedSampleRateRanges() {
+ return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+ }
- /**
- * Returns the video capabilities or {@code null} if this is not a video codec.
- */
- public VideoCapabilities getVideoCapabilities() {
- return mVideoCaps;
- }
+ public Range<Integer>[] getInputChannelCountRanges() {
+ return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
+ }
- /** @hide */
- public CodecCapabilities dup() {
- CodecCapabilities caps = new CodecCapabilities();
+ public int getMaxInputChannelCount() {
+ return native_getMaxInputChannelCount();
+ }
- // profileLevels and colorFormats may be modified by client.
- caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
- caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
+ public int getMinInputChannelCount() {
+ return native_getMinInputChannelCount();
+ }
- caps.mMime = mMime;
- caps.mMaxSupportedInstances = mMaxSupportedInstances;
- caps.mFlagsRequired = mFlagsRequired;
- caps.mFlagsSupported = mFlagsSupported;
- caps.mFlagsVerified = mFlagsVerified;
- caps.mAudioCaps = mAudioCaps;
- caps.mVideoCaps = mVideoCaps;
- caps.mEncoderCaps = mEncoderCaps;
- caps.mDefaultFormat = mDefaultFormat;
- caps.mCapabilitiesInfo = mCapabilitiesInfo;
+ public boolean isSampleRateSupported(int sampleRate) {
+ return native_isSampleRateSupported(sampleRate);
+ }
- return caps;
- }
+ // This API is for internal Java implementation only. Should not be called.
+ public void getDefaultFormat(MediaFormat format) {
+ throw new UnsupportedOperationException(
+ "Java Implementation should not call native implemenatation");
+ }
- /**
- * Retrieve the codec capabilities for a certain {@code mime type}, {@code
- * profile} and {@code level}. If the type, or profile-level combination
- * is not understood by the framework, it returns null.
- * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
- * method without calling any method of the {@link MediaCodecList} class beforehand
- * results in a {@link NullPointerException}.</p>
- */
- public static CodecCapabilities createFromProfileLevel(
- String mime, int profile, int level) {
- CodecProfileLevel pl = new CodecProfileLevel();
- pl.profile = profile;
- pl.level = level;
- MediaFormat defaultFormat = new MediaFormat();
- defaultFormat.setString(MediaFormat.KEY_MIME, mime);
-
- CodecCapabilities ret = new CodecCapabilities(
- new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
- defaultFormat, new MediaFormat() /* info */);
- if (ret.mError != 0) {
- return null;
+ // This API is for internal Java implementation only. Should not be called.
+ public boolean supportsFormat(MediaFormat format) {
+ throw new UnsupportedOperationException(
+ "Java Implementation should not call native implemenatation");
}
- return ret;
- }
- /* package private */ CodecCapabilities(
- CodecProfileLevel[] profLevs, int[] colFmts,
- boolean encoder,
- Map<String, Object>defaultFormatMap,
- Map<String, Object>capabilitiesMap) {
- this(profLevs, colFmts, encoder,
- new MediaFormat(defaultFormatMap),
- new MediaFormat(capabilitiesMap));
- }
+ private native int native_getMaxInputChannelCount();
+ private native int native_getMinInputChannelCount();
+ private native boolean native_isSampleRateSupported(int sampleRate);
+ private static native void native_init();
- private MediaFormat mCapabilitiesInfo;
-
- /* package private */ CodecCapabilities(
- CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
- MediaFormat defaultFormat, MediaFormat info) {
- final Map<String, Object> map = info.getMap();
- colorFormats = colFmts;
- mFlagsVerified = 0; // TODO: remove as it is unused
- mDefaultFormat = defaultFormat;
- mCapabilitiesInfo = info;
- mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
-
- /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
- supported profiles. Determine the level for them using the info they provide. */
- if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
- CodecProfileLevel profLev = new CodecProfileLevel();
- profLev.profile = CodecProfileLevel.VP9Profile0;
- profLev.level = VideoCapabilities.equivalentVP9Level(info);
- profLevs = new CodecProfileLevel[] { profLev };
- }
- profileLevels = profLevs;
-
- if (mMime.toLowerCase().startsWith("audio/")) {
- mAudioCaps = AudioCapabilities.create(info, this);
- mAudioCaps.getDefaultFormat(mDefaultFormat);
- } else if (mMime.toLowerCase().startsWith("video/")
- || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
- mVideoCaps = VideoCapabilities.create(info, this);
- }
- if (encoder) {
- mEncoderCaps = EncoderCapabilities.create(info, this);
- mEncoderCaps.getDefaultFormat(mDefaultFormat);
- }
-
- final Map<String, Object> global = MediaCodecList.getGlobalSettings();
- mMaxSupportedInstances = Utils.parseIntSafely(
- global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
-
- int maxInstances = Utils.parseIntSafely(
- map.get("max-concurrent-instances"), mMaxSupportedInstances);
- mMaxSupportedInstances =
- Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
-
- for (Feature feat: getValidFeatures()) {
- String key = MediaFormat.KEY_FEATURE_ + feat.mName;
- Integer yesNo = (Integer)map.get(key);
- if (yesNo == null) {
- continue;
- }
- if (yesNo > 0) {
- mFlagsRequired |= feat.mValue;
- }
- mFlagsSupported |= feat.mValue;
- if (!feat.mInternal) {
- mDefaultFormat.setInteger(key, 1);
- }
- // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
}
}
- }
- /**
- * A class that supports querying the audio capabilities of a codec.
- */
- public static final class AudioCapabilities {
- private static final String TAG = "AudioCapabilities";
- private CodecCapabilities mParent;
- private Range<Integer> mBitrateRange;
+ private AudioCapsIntf mImpl;
+
+ /** @hide */
+ public static AudioCapabilities create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent);
+ AudioCapabilities caps = new AudioCapabilities(impl);
+ return caps;
+ }
- private int[] mSampleRates;
- private Range<Integer>[] mSampleRateRanges;
- private Range<Integer>[] mInputChannelRanges;
+ /* package private */ AudioCapabilities(AudioCapsIntf impl) {
+ mImpl = impl;
+ }
- private static final int MAX_INPUT_CHANNEL_COUNT = 30;
+ /* no public constructor */
+ private AudioCapabilities() { }
/**
* Returns the range of supported bitrates in bits/second.
*/
public Range<Integer> getBitrateRange() {
- return mBitrateRange;
+ return mImpl.getBitrateRange();
}
/**
@@ -1433,7 +2169,7 @@ public final class MediaCodecInfo {
* {@code null}. The array is sorted in ascending order.
*/
public int[] getSupportedSampleRates() {
- return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null;
+ return mImpl.getSupportedSampleRates();
}
/**
@@ -1442,7 +2178,21 @@ public final class MediaCodecInfo {
* distinct.
*/
public Range<Integer>[] getSupportedSampleRateRanges() {
- return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+ return mImpl.getSupportedSampleRateRanges();
+ }
+
+ /*
+ * Returns an array of ranges representing the number of input channels supported.
+ * The codec supports any number of input channels within this range.
+ *
+ * This supersedes the {@link #getMaxInputChannelCount} method.
+ *
+ * For many codecs, this will be a single range [1..N], for some N.
+ */
+ @SuppressLint("ArrayReturn")
+ @NonNull
+ public Range<Integer>[] getInputChannelCountRanges() {
+ return mImpl.getInputChannelCountRanges();
}
/**
@@ -1462,14 +2212,7 @@ public final class MediaCodecInfo {
*/
@IntRange(from = 1, to = 255)
public int getMaxInputChannelCount() {
- int overall_max = 0;
- for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
- int lmax = mInputChannelRanges[i].getUpper();
- if (lmax > overall_max) {
- overall_max = lmax;
- }
- }
- return overall_max;
+ return mImpl.getMaxInputChannelCount();
}
/**
@@ -1481,364 +2224,24 @@ public final class MediaCodecInfo {
*/
@IntRange(from = 1, to = 255)
public int getMinInputChannelCount() {
- int overall_min = MAX_INPUT_CHANNEL_COUNT;
- for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
- int lmin = mInputChannelRanges[i].getLower();
- if (lmin < overall_min) {
- overall_min = lmin;
- }
- }
- return overall_min;
- }
-
- /*
- * Returns an array of ranges representing the number of input channels supported.
- * The codec supports any number of input channels within this range.
- *
- * This supersedes the {@link #getMaxInputChannelCount} method.
- *
- * For many codecs, this will be a single range [1..N], for some N.
- */
- @SuppressLint("ArrayReturn")
- @NonNull
- public Range<Integer>[] getInputChannelCountRanges() {
- return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
- }
-
- /* no public constructor */
- private AudioCapabilities() { }
-
- /** @hide */
- public static AudioCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- AudioCapabilities caps = new AudioCapabilities();
- caps.init(info, parent);
- return caps;
- }
-
- private void init(MediaFormat info, CodecCapabilities parent) {
- mParent = parent;
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- }
-
- private void initWithPlatformLimits() {
- mBitrateRange = Range.create(0, Integer.MAX_VALUE);
- mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
- // mBitrateRange = Range.create(1, 320000);
- final int minSampleRate = SystemProperties.
- getInt("ro.mediacodec.min_sample_rate", 7350);
- final int maxSampleRate = SystemProperties.
- getInt("ro.mediacodec.max_sample_rate", 192000);
- mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
- mSampleRates = null;
- }
-
- private boolean supports(Integer sampleRate, Integer inputChannels) {
- // channels and sample rates are checked orthogonally
- if (inputChannels != null) {
- int ix = Utils.binarySearchDistinctRanges(
- mInputChannelRanges, inputChannels);
- if (ix < 0) {
- return false;
- }
- }
- if (sampleRate != null) {
- int ix = Utils.binarySearchDistinctRanges(
- mSampleRateRanges, sampleRate);
- if (ix < 0) {
- return false;
- }
- }
- return true;
+ return mImpl.getMinInputChannelCount();
}
/**
* Query whether the sample rate is supported by the codec.
*/
public boolean isSampleRateSupported(int sampleRate) {
- return supports(sampleRate, null);
- }
-
- /** modifies rates */
- private void limitSampleRates(int[] rates) {
- Arrays.sort(rates);
- ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
- for (int rate: rates) {
- if (supports(rate, null /* channels */)) {
- ranges.add(Range.create(rate, rate));
- }
- }
- mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
- createDiscreteSampleRates();
- }
-
- private void createDiscreteSampleRates() {
- mSampleRates = new int[mSampleRateRanges.length];
- for (int i = 0; i < mSampleRateRanges.length; i++) {
- mSampleRates[i] = mSampleRateRanges[i].getLower();
- }
- }
-
- /** modifies rateRanges */
- private void limitSampleRates(Range<Integer>[] rateRanges) {
- sortDistinctRanges(rateRanges);
- mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
- // check if all values are discrete
- for (Range<Integer> range: mSampleRateRanges) {
- if (!range.getLower().equals(range.getUpper())) {
- mSampleRates = null;
- return;
- }
- }
- createDiscreteSampleRates();
- }
-
- private void applyLevelLimits() {
- int[] sampleRates = null;
- Range<Integer> sampleRateRange = null, bitRates = null;
- int maxChannels = MAX_INPUT_CHANNEL_COUNT;
- CodecProfileLevel[] profileLevels = mParent.profileLevels;
- String mime = mParent.getMimeType();
-
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
- sampleRates = new int[] {
- 8000, 11025, 12000,
- 16000, 22050, 24000,
- 32000, 44100, 48000 };
- bitRates = Range.create(8000, 320000);
- maxChannels = 2;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(4750, 12200);
- maxChannels = 1;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
- sampleRates = new int[] { 16000 };
- bitRates = Range.create(6600, 23850);
- maxChannels = 1;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
- sampleRates = new int[] {
- 7350, 8000,
- 11025, 12000, 16000,
- 22050, 24000, 32000,
- 44100, 48000, 64000,
- 88200, 96000 };
- bitRates = Range.create(8000, 510000);
- maxChannels = 48;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
- bitRates = Range.create(32000, 500000);
- sampleRateRange = Range.create(8000, 192000);
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
- bitRates = Range.create(6000, 510000);
- sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
- sampleRateRange = Range.create(1, 192000);
- bitRates = Range.create(1, 10000000);
- maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
- sampleRateRange = Range.create(1, 655350);
- // lossless codec, so bitrate is ignored
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(64000, 64000);
- // platform allows multiple channels for this format
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(13000, 13000);
- maxChannels = 1;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
- maxChannels = 6;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
- maxChannels = 16;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
- sampleRates = new int[] { 48000 };
- bitRates = Range.create(32000, 6144000);
- maxChannels = 16;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
- sampleRates = new int[] { 44100, 48000, 96000, 192000 };
- bitRates = Range.create(16000, 2688000);
- maxChannels = 24;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
- sampleRates = new int[] { 44100, 48000 };
- bitRates = Range.create(96000, 1524000);
- maxChannels = 6;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
- for (CodecProfileLevel profileLevel: profileLevels) {
- switch (profileLevel.profile) {
- case CodecProfileLevel.DTS_HDProfileLBR:
- sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
- bitRates = Range.create(32000, 768000);
- break;
- case CodecProfileLevel.DTS_HDProfileHRA:
- case CodecProfileLevel.DTS_HDProfileMA:
- sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
- bitRates = Range.create(96000, 24500000);
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- mParent.mError |= ERROR_UNRECOGNIZED;
- sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
- bitRates = Range.create(96000, 24500000);
- }
- }
- maxChannels = 8;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
- for (CodecProfileLevel profileLevel: profileLevels) {
- switch (profileLevel.profile) {
- case CodecProfileLevel.DTS_UHDProfileP2:
- sampleRates = new int[]{ 48000 };
- bitRates = Range.create(96000, 768000);
- maxChannels = 10;
- break;
- case CodecProfileLevel.DTS_UHDProfileP1:
- sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
- bitRates = Range.create(96000, 24500000);
- maxChannels = 32;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- mParent.mError |= ERROR_UNRECOGNIZED;
- sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
- bitRates = Range.create(96000, 24500000);
- maxChannels = 32;
- }
- }
- } else {
- Log.w(TAG, "Unsupported mime " + mime);
- mParent.mError |= ERROR_UNSUPPORTED;
- }
-
- // restrict ranges
- if (sampleRates != null) {
- limitSampleRates(sampleRates);
- } else if (sampleRateRange != null) {
- limitSampleRates(new Range[] { sampleRateRange });
- }
-
- Range<Integer> channelRange = Range.create(1, maxChannels);
-
- applyLimits(new Range[] { channelRange }, bitRates);
- }
-
- private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
-
- // clamp & make a local copy
- Range<Integer>[] myInputChannels = new Range[inputChannels.length];
- for (int i = 0; i < inputChannels.length; i++) {
- int lower = inputChannels[i].clamp(1);
- int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
- myInputChannels[i] = Range.create(lower, upper);
- }
-
- // sort, intersect with existing, & save channel list
- sortDistinctRanges(myInputChannels);
- Range<Integer>[] joinedChannelList =
- intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
- mInputChannelRanges = joinedChannelList;
-
- if (bitRates != null) {
- mBitrateRange = mBitrateRange.intersect(bitRates);
- }
- }
-
- private void parseFromInfo(MediaFormat info) {
- int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
- Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
- Range<Integer> bitRates = POSITIVE_INTEGERS;
-
- if (info.containsKey("sample-rate-ranges")) {
- String[] rateStrings = info.getString("sample-rate-ranges").split(",");
- Range<Integer>[] rateRanges = new Range[rateStrings.length];
- for (int i = 0; i < rateStrings.length; i++) {
- rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
- }
- limitSampleRates(rateRanges);
- }
-
- // we will prefer channel-ranges over max-channel-count
- if (info.containsKey("channel-ranges")) {
- String[] channelStrings = info.getString("channel-ranges").split(",");
- Range<Integer>[] channelRanges = new Range[channelStrings.length];
- for (int i = 0; i < channelStrings.length; i++) {
- channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
- }
- channels = channelRanges;
- } else if (info.containsKey("channel-range")) {
- Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
- null);
- channels = new Range[] { oneRange };
- } else if (info.containsKey("max-channel-count")) {
- maxInputChannels = Utils.parseIntSafely(
- info.getString("max-channel-count"), maxInputChannels);
- if (maxInputChannels == 0) {
- channels = new Range[] {Range.create(0, 0)};
- } else {
- channels = new Range[] {Range.create(1, maxInputChannels)};
- }
- } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
- maxInputChannels = 0;
- channels = new Range[] {Range.create(0, 0)};
- }
-
- if (info.containsKey("bitrate-range")) {
- bitRates = bitRates.intersect(
- Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
- }
-
- applyLimits(channels, bitRates);
+ return mImpl.isSampleRateSupported(sampleRate);
}
/** @hide */
public void getDefaultFormat(MediaFormat format) {
- // report settings that have only a single choice
- if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
- format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
- }
- if (getMaxInputChannelCount() == 1) {
- // mono-only format
- format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
- }
- if (mSampleRates != null && mSampleRates.length == 1) {
- format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
- }
+ mImpl.getDefaultFormat(format);
}
- /* package private */
- // must not contain KEY_PROFILE
- static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
- // We don't set level-specific limits for audio codecs today. Key candidates would
- // be sample rate, bit rate or channel count.
- // MediaFormat.KEY_SAMPLE_RATE,
- // MediaFormat.KEY_CHANNEL_COUNT,
- // MediaFormat.KEY_BIT_RATE,
- MediaFormat.KEY_MIME);
-
/** @hide */
public boolean supportsFormat(MediaFormat format) {
- Map<String, Object> map = format.getMap();
- Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
- Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
-
- if (!supports(sampleRate, channels)) {
- return false;
- }
-
- if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
- return false;
- }
-
- // nothing to do for:
- // KEY_CHANNEL_MASK: codecs don't get this
- // KEY_IS_ADTS: required feature for all AAC decoders
- return true;
+ return mImpl.supportsFormat(format);
}
}
@@ -1898,304 +2301,6 @@ public final class MediaCodecInfo {
*/
public static final class VideoCapabilities {
private static final String TAG = "VideoCapabilities";
- private CodecCapabilities mParent;
- private Range<Integer> mBitrateRange;
-
- private Range<Integer> mHeightRange;
- private Range<Integer> mWidthRange;
- private Range<Integer> mBlockCountRange;
- private Range<Integer> mHorizontalBlockRange;
- private Range<Integer> mVerticalBlockRange;
- private Range<Rational> mAspectRatioRange;
- private Range<Rational> mBlockAspectRatioRange;
- private Range<Long> mBlocksPerSecondRange;
- private Map<Size, Range<Long>> mMeasuredFrameRates;
- private List<PerformancePoint> mPerformancePoints;
- private Range<Integer> mFrameRateRange;
-
- private int mBlockWidth;
- private int mBlockHeight;
- private int mWidthAlignment;
- private int mHeightAlignment;
- private int mSmallerDimensionUpperLimit;
-
- private boolean mAllowMbOverride; // allow XML to override calculated limits
-
- /**
- * Returns the range of supported bitrates in bits/second.
- */
- public Range<Integer> getBitrateRange() {
- return mBitrateRange;
- }
-
- /**
- * Returns the range of supported video widths.
- * <p class=note>
- * 32-bit processes will not support resolutions larger than 4096x4096 due to
- * the limited address space.
- */
- public Range<Integer> getSupportedWidths() {
- return mWidthRange;
- }
-
- /**
- * Returns the range of supported video heights.
- * <p class=note>
- * 32-bit processes will not support resolutions larger than 4096x4096 due to
- * the limited address space.
- */
- public Range<Integer> getSupportedHeights() {
- return mHeightRange;
- }
-
- /**
- * Returns the alignment requirement for video width (in pixels).
- *
- * This is a power-of-2 value that video width must be a
- * multiple of.
- */
- public int getWidthAlignment() {
- return mWidthAlignment;
- }
-
- /**
- * Returns the alignment requirement for video height (in pixels).
- *
- * This is a power-of-2 value that video height must be a
- * multiple of.
- */
- public int getHeightAlignment() {
- return mHeightAlignment;
- }
-
- /**
- * Return the upper limit on the smaller dimension of width or height.
- * <p></p>
- * Some codecs have a limit on the smaller dimension, whether it be
- * the width or the height. E.g. a codec may only be able to handle
- * up to 1920x1080 both in landscape and portrait mode (1080x1920).
- * In this case the maximum width and height are both 1920, but the
- * smaller dimension limit will be 1080. For other codecs, this is
- * {@code Math.min(getSupportedWidths().getUpper(),
- * getSupportedHeights().getUpper())}.
- *
- * @hide
- */
- public int getSmallerDimensionUpperLimit() {
- return mSmallerDimensionUpperLimit;
- }
-
- /**
- * Returns the range of supported frame rates.
- * <p>
- * This is not a performance indicator. Rather, it expresses the
- * limits specified in the coding standard, based on the complexities
- * of encoding material for later playback at a certain frame rate,
- * or the decoding of such material in non-realtime.
- */
- public Range<Integer> getSupportedFrameRates() {
- return mFrameRateRange;
- }
-
- /**
- * Returns the range of supported video widths for a video height.
- * @param height the height of the video
- */
- public Range<Integer> getSupportedWidthsFor(int height) {
- try {
- Range<Integer> range = mWidthRange;
- if (!mHeightRange.contains(height)
- || (height % mHeightAlignment) != 0) {
- throw new IllegalArgumentException("unsupported height");
- }
- final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-
- // constrain by block count and by block aspect ratio
- final int minWidthInBlocks = Math.max(
- Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
- (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
- * heightInBlocks));
- final int maxWidthInBlocks = Math.min(
- mBlockCountRange.getUpper() / heightInBlocks,
- (int)(mBlockAspectRatioRange.getUpper().doubleValue()
- * heightInBlocks));
- range = range.intersect(
- (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
- maxWidthInBlocks * mBlockWidth);
-
- // constrain by smaller dimension limit
- if (height > mSmallerDimensionUpperLimit) {
- range = range.intersect(1, mSmallerDimensionUpperLimit);
- }
-
- // constrain by aspect ratio
- range = range.intersect(
- (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
- * height),
- (int)(mAspectRatioRange.getUpper().doubleValue() * height));
- return range;
- } catch (IllegalArgumentException e) {
- // height is not supported because there are no suitable widths
- Log.v(TAG, "could not get supported widths for " + height);
- throw new IllegalArgumentException("unsupported height");
- }
- }
-
- /**
- * Returns the range of supported video heights for a video width
- * @param width the width of the video
- */
- public Range<Integer> getSupportedHeightsFor(int width) {
- try {
- Range<Integer> range = mHeightRange;
- if (!mWidthRange.contains(width)
- || (width % mWidthAlignment) != 0) {
- throw new IllegalArgumentException("unsupported width");
- }
- final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-
- // constrain by block count and by block aspect ratio
- final int minHeightInBlocks = Math.max(
- Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
- (int)Math.ceil(widthInBlocks /
- mBlockAspectRatioRange.getUpper().doubleValue()));
- final int maxHeightInBlocks = Math.min(
- mBlockCountRange.getUpper() / widthInBlocks,
- (int)(widthInBlocks /
- mBlockAspectRatioRange.getLower().doubleValue()));
- range = range.intersect(
- (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
- maxHeightInBlocks * mBlockHeight);
-
- // constrain by smaller dimension limit
- if (width > mSmallerDimensionUpperLimit) {
- range = range.intersect(1, mSmallerDimensionUpperLimit);
- }
-
- // constrain by aspect ratio
- range = range.intersect(
- (int)Math.ceil(width /
- mAspectRatioRange.getUpper().doubleValue()),
- (int)(width / mAspectRatioRange.getLower().doubleValue()));
- return range;
- } catch (IllegalArgumentException e) {
- // width is not supported because there are no suitable heights
- Log.v(TAG, "could not get supported heights for " + width);
- throw new IllegalArgumentException("unsupported width");
- }
- }
-
- /**
- * Returns the range of supported video frame rates for a video size.
- * <p>
- * This is not a performance indicator. Rather, it expresses the limits specified in
- * the coding standard, based on the complexities of encoding material of a given
- * size for later playback at a certain frame rate, or the decoding of such material
- * in non-realtime.
-
- * @param width the width of the video
- * @param height the height of the video
- */
- public Range<Double> getSupportedFrameRatesFor(int width, int height) {
- Range<Integer> range = mHeightRange;
- if (!supports(width, height, null)) {
- throw new IllegalArgumentException("unsupported size");
- }
- final int blockCount =
- Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-
- return Range.create(
- Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
- (double) mFrameRateRange.getLower()),
- Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
- (double) mFrameRateRange.getUpper()));
- }
-
- private int getBlockCount(int width, int height) {
- return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
- }
-
- @NonNull
- private Size findClosestSize(int width, int height) {
- int targetBlockCount = getBlockCount(width, height);
- Size closestSize = null;
- int minDiff = Integer.MAX_VALUE;
- for (Size size : mMeasuredFrameRates.keySet()) {
- int diff = Math.abs(targetBlockCount -
- getBlockCount(size.getWidth(), size.getHeight()));
- if (diff < minDiff) {
- minDiff = diff;
- closestSize = size;
- }
- }
- return closestSize;
- }
-
- private Range<Double> estimateFrameRatesFor(int width, int height) {
- Size size = findClosestSize(width, height);
- Range<Long> range = mMeasuredFrameRates.get(size);
- Double ratio = getBlockCount(size.getWidth(), size.getHeight())
- / (double)Math.max(getBlockCount(width, height), 1);
- return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
- }
-
- /**
- * Returns the range of achievable video frame rates for a video size.
- * May return {@code null}, if the codec did not publish any measurement
- * data.
- * <p>
- * This is a performance estimate provided by the device manufacturer based on statistical
- * sampling of full-speed decoding and encoding measurements in various configurations
- * of common video sizes supported by the codec. As such it should only be used to
- * compare individual codecs on the device. The value is not suitable for comparing
- * different devices or even different android releases for the same device.
- * <p>
- * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
- * corresponds to the fastest frame rates achieved in the tested configurations. As
- * such, it should not be used to gauge guaranteed or even average codec performance
- * on the device.
- * <p>
- * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
- * corresponds closer to sustained performance <em>in tested configurations</em>.
- * One can expect to achieve sustained performance higher than the lower limit more than
- * 50% of the time, and higher than half of the lower limit at least 90% of the time
- * <em>in tested configurations</em>.
- * Conversely, one can expect performance lower than twice the upper limit at least
- * 90% of the time.
- * <p class=note>
- * Tested configurations use a single active codec. For use cases where multiple
- * codecs are active, applications can expect lower and in most cases significantly lower
- * performance.
- * <p class=note>
- * The returned range value is interpolated from the nearest frame size(s) tested.
- * Codec performance is severely impacted by other activity on the device as well
- * as environmental factors (such as battery level, temperature or power source), and can
- * vary significantly even in a steady environment.
- * <p class=note>
- * Use this method in cases where only codec performance matters, e.g. to evaluate if
- * a codec has any chance of meeting a performance target. Codecs are listed
- * in {@link MediaCodecList} in the preferred order as defined by the device
- * manufacturer. As such, applications should use the first suitable codec in the
- * list to achieve the best balance between power use and performance.
- *
- * @param width the width of the video
- * @param height the height of the video
- *
- * @throws IllegalArgumentException if the video size is not supported.
- */
- @Nullable
- public Range<Double> getAchievableFrameRatesFor(int width, int height) {
- if (!supports(width, height, null)) {
- throw new IllegalArgumentException("unsupported size");
- }
-
- if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
- Log.w(TAG, "Codec did not publish any measurement data.");
- return null;
- }
-
- return estimateFrameRatesFor(width, height);
- }
/**
* Video performance points are a set of standard performance points defined by number of
@@ -2225,6 +2330,24 @@ public final class MediaCodecInfo {
}
/**
+ * Width in macroblocks.
+ *
+ * @hide
+ */
+ /** package private */ int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Height in macroblocks.
+ *
+ * @hide
+ */
+ /** package private */ int getHeight() {
+ return mHeight;
+ }
+
+ /**
* Maximum frame rate in frames per second.
*
* @hide
@@ -2244,6 +2367,24 @@ public final class MediaCodecInfo {
return mMaxMacroBlockRate;
}
+ /**
+ * Codec block width in macroblocks.
+ *
+ * @hide
+ */
+ /** package private */ int getBlockWidth() {
+ return mBlockSize.getWidth();
+ }
+
+ /**
+ * Codec block height in macroblocks.
+ *
+ * @hide
+ */
+ /** package private */ int getBlockHeight() {
+ return mBlockSize.getHeight();
+ }
+
/** Convert to a debug string */
public String toString() {
int blockWidth = 16 * mBlockSize.getWidth();
@@ -2331,6 +2472,20 @@ public final class MediaCodecInfo {
this(width, height, frameRate, frameRate /* maxFrameRate */, new Size(16, 16));
}
+ /* package private */ PerformancePoint(int width, int height, int maxFrameRate,
+ long maxMacroBlockRate, int blockSizeWidth, int blockSizeHeight) {
+ mWidth = width;
+ mHeight = height;
+ mMaxFrameRate = maxFrameRate;
+ mMaxMacroBlockRate = maxMacroBlockRate;
+ mBlockSize = new Size(blockSizeWidth, blockSizeHeight);
+ }
+
+ private PerformancePoint(PerformancePoint pp) {
+ this(pp.mWidth, pp.mHeight, pp.mMaxFrameRate, pp.mMaxMacroBlockRate,
+ pp.mBlockSize.getWidth(), pp.mBlockSize.getHeight());
+ }
+
/** Saturates a long value to int */
private int saturateLongToInt(long value) {
if (value < Integer.MIN_VALUE) {
@@ -2384,14 +2539,18 @@ public final class MediaCodecInfo {
* @return {@code true} if the performance point covers the other.
*/
public boolean covers(@NonNull PerformancePoint other) {
- // convert performance points to common block size
- Size commonSize = getCommonBlockSize(other);
- PerformancePoint aligned = new PerformancePoint(this, commonSize);
- PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ return native_covers(other);
+ } else {
+ // convert performance points to common block size
+ Size commonSize = getCommonBlockSize(other);
+ PerformancePoint aligned = new PerformancePoint(this, commonSize);
+ PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
- return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
- && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
- && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
+ return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
+ && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
+ && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
+ }
}
private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) {
@@ -2405,17 +2564,28 @@ public final class MediaCodecInfo {
if (o instanceof PerformancePoint) {
// convert performance points to common block size
PerformancePoint other = (PerformancePoint)o;
- Size commonSize = getCommonBlockSize(other);
- PerformancePoint aligned = new PerformancePoint(this, commonSize);
- PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ return native_equals(other);
+ } else {
+ Size commonSize = getCommonBlockSize(other);
+ PerformancePoint aligned = new PerformancePoint(this, commonSize);
+ PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
- return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
- && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
- && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
+ return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
+ && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
+ && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
+ }
}
return false;
}
+ private native boolean native_covers(PerformancePoint other);
+ private native boolean native_equals(PerformancePoint other);
+
+ static {
+ System.loadLibrary("media_jni");
+ }
+
/** 480p 24fps */
@NonNull
public static final PerformancePoint SD_24 = new PerformancePoint(720, 480, 24);
@@ -2520,1351 +2690,1870 @@ public final class MediaCodecInfo {
public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240);
}
- /**
- * Returns the supported performance points. May return {@code null} if the codec did not
- * publish any performance point information (e.g. the vendor codecs have not been updated
- * to the latest android release). May return an empty list if the codec published that
- * if does not guarantee any performance points.
- * <p>
- * This is a performance guarantee provided by the device manufacturer for hardware codecs
- * based on hardware capabilities of the device.
- * <p>
- * The returned list is sorted first by decreasing number of pixels, then by decreasing
- * width, and finally by decreasing frame rate.
- * Performance points assume a single active codec. For use cases where multiple
- * codecs are active, should use that highest pixel count, and add the frame rates of
- * each individual codec.
- * <p class=note>
- * 32-bit processes will not support resolutions larger than 4096x4096 due to
- * the limited address space, but performance points will be presented as is.
- * In other words, even though a component publishes a performance point for
- * a resolution higher than 4096x4096, it does not mean that the resolution is supported
- * for 32-bit processes.
- */
- @Nullable
- public List<PerformancePoint> getSupportedPerformancePoints() {
- return mPerformancePoints;
- }
+ /* package private */ interface VideoCapsIntf {
+ public Range<Integer> getBitrateRange();
- /**
- * Returns whether a given video size ({@code width} and
- * {@code height}) and {@code frameRate} combination is supported.
- */
- public boolean areSizeAndRateSupported(
- int width, int height, double frameRate) {
- return supports(width, height, frameRate);
- }
+ public Range<Integer> getSupportedWidths();
- /**
- * Returns whether a given video size ({@code width} and
- * {@code height}) is supported.
- */
- public boolean isSizeSupported(int width, int height) {
- return supports(width, height, null);
- }
+ public Range<Integer> getSupportedHeights();
- private boolean supports(Integer width, Integer height, Number rate) {
- boolean ok = true;
+ public int getWidthAlignment();
- if (ok && width != null) {
- ok = mWidthRange.contains(width)
- && (width % mWidthAlignment == 0);
- }
- if (ok && height != null) {
- ok = mHeightRange.contains(height)
- && (height % mHeightAlignment == 0);
- }
- if (ok && rate != null) {
- ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
- }
- if (ok && height != null && width != null) {
- ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+ public int getHeightAlignment();
- final int widthInBlocks = Utils.divUp(width, mBlockWidth);
- final int heightInBlocks = Utils.divUp(height, mBlockHeight);
- final int blockCount = widthInBlocks * heightInBlocks;
- ok = ok && mBlockCountRange.contains(blockCount)
- && mBlockAspectRatioRange.contains(
- new Rational(widthInBlocks, heightInBlocks))
- && mAspectRatioRange.contains(new Rational(width, height));
- if (ok && rate != null) {
- double blocksPerSec = blockCount * rate.doubleValue();
- ok = mBlocksPerSecondRange.contains(
- Utils.longRangeFor(blocksPerSec));
- }
- }
- return ok;
- }
+ public int getSmallerDimensionUpperLimit();
- /* package private */
- // must not contain KEY_PROFILE
- static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
- MediaFormat.KEY_WIDTH,
- MediaFormat.KEY_HEIGHT,
- MediaFormat.KEY_FRAME_RATE,
- MediaFormat.KEY_BIT_RATE,
- MediaFormat.KEY_MIME);
+ public Range<Integer> getSupportedFrameRates();
- /**
- * @hide
- * @throws java.lang.ClassCastException */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
- Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
- Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
+ public Range<Integer> getSupportedWidthsFor(int height);
- if (!supports(width, height, rate)) {
- return false;
- }
+ public Range<Integer> getSupportedHeightsFor(int width);
- if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
- return false;
- }
+ public Range<Double> getSupportedFrameRatesFor(int width, int height);
- // we ignore color-format for now as it is not reliably reported by codec
- return true;
- }
+ public Range<Double> getAchievableFrameRatesFor(int width, int height);
- /* no public constructor */
- private VideoCapabilities() { }
+ public boolean areSizeAndRateSupported(int width, int height, double frameRate);
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public static VideoCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- VideoCapabilities caps = new VideoCapabilities();
- caps.init(info, parent);
- return caps;
- }
+ public boolean isSizeSupported(int width, int height);
- private void init(MediaFormat info, CodecCapabilities parent) {
- mParent = parent;
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- updateLimits();
- }
+ public boolean supportsFormat(MediaFormat format);
- /** @hide */
- public Size getBlockSize() {
- return new Size(mBlockWidth, mBlockHeight);
+ public List<PerformancePoint> getSupportedPerformancePoints();
}
- /** @hide */
- public Range<Integer> getBlockCountRange() {
- return mBlockCountRange;
- }
+ /* package private */ static final class VideoCapsLegacyImpl implements VideoCapsIntf {
+ /* package private */
+ // must not contain KEY_PROFILE
+ static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+ MediaFormat.KEY_WIDTH,
+ MediaFormat.KEY_HEIGHT,
+ MediaFormat.KEY_FRAME_RATE,
+ MediaFormat.KEY_BIT_RATE,
+ MediaFormat.KEY_MIME);
+
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
+ private Range<Integer> mBitrateRange;
+
+ private Range<Integer> mHeightRange;
+ private Range<Integer> mWidthRange;
+ private Range<Integer> mBlockCountRange;
+ private Range<Integer> mHorizontalBlockRange;
+ private Range<Integer> mVerticalBlockRange;
+ private Range<Rational> mAspectRatioRange;
+ private Range<Rational> mBlockAspectRatioRange;
+ private Range<Long> mBlocksPerSecondRange;
+ private Map<Size, Range<Long>> mMeasuredFrameRates;
+ private List<PerformancePoint> mPerformancePoints;
+ private Range<Integer> mFrameRateRange;
+
+ private int mBlockWidth;
+ private int mBlockHeight;
+ private int mWidthAlignment;
+ private int mHeightAlignment;
+ private int mSmallerDimensionUpperLimit;
+
+ private boolean mAllowMbOverride; // allow XML to override calculated limits
+
+ /* no public constructor */
+ private VideoCapsLegacyImpl() { }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static VideoCapsLegacyImpl create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ Log.d(TAG, "Legacy implementation is called while native flag is on.");
+ }
- /** @hide */
- public Range<Long> getBlocksPerSecondRange() {
- return mBlocksPerSecondRange;
- }
+ VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl();
+ caps.init(info, parent);
+ return caps;
+ }
- /** @hide */
- public Range<Rational> getAspectRatioRange(boolean blocks) {
- return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
- }
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ updateLimits();
+ }
- private void initWithPlatformLimits() {
- mBitrateRange = BITRATE_RANGE;
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
- mWidthRange = getSizeRange();
- mHeightRange = getSizeRange();
- mFrameRateRange = FRAME_RATE_RANGE;
+ public Range<Integer> getSupportedWidths() {
+ return mWidthRange;
+ }
- mHorizontalBlockRange = getSizeRange();
- mVerticalBlockRange = getSizeRange();
+ public Range<Integer> getSupportedHeights() {
+ return mHeightRange;
+ }
- // full positive ranges are supported as these get calculated
- mBlockCountRange = POSITIVE_INTEGERS;
- mBlocksPerSecondRange = POSITIVE_LONGS;
+ public int getWidthAlignment() {
+ return mWidthAlignment;
+ }
- mBlockAspectRatioRange = POSITIVE_RATIONALS;
- mAspectRatioRange = POSITIVE_RATIONALS;
+ public int getHeightAlignment() {
+ return mHeightAlignment;
+ }
- mWidthAlignment = 1;
- mHeightAlignment = 1;
- mBlockWidth = 1;
- mBlockHeight = 1;
- mSmallerDimensionUpperLimit = getSizeRange().getUpper();
- }
+ /** @hide */
+ public int getSmallerDimensionUpperLimit() {
+ return mSmallerDimensionUpperLimit;
+ }
+
+ public Range<Integer> getSupportedFrameRates() {
+ return mFrameRateRange;
+ }
- private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
- Vector<PerformancePoint> ret = new Vector<>();
- final String prefix = "performance-point-";
- Set<String> keys = map.keySet();
- for (String key : keys) {
- // looking for: performance-point-WIDTHxHEIGHT-range
- if (!key.startsWith(prefix)) {
- continue;
+ public Range<Integer> getSupportedWidthsFor(int height) {
+ try {
+ Range<Integer> range = mWidthRange;
+ if (!mHeightRange.contains(height)
+ || (height % mHeightAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported height");
+ }
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+
+ // constrain by block count and by block aspect ratio
+ final int minWidthInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
+ (int) Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
+ * heightInBlocks));
+ final int maxWidthInBlocks = Math.min(
+ mBlockCountRange.getUpper() / heightInBlocks,
+ (int) (mBlockAspectRatioRange.getUpper().doubleValue()
+ * heightInBlocks));
+ range = range.intersect(
+ (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
+ maxWidthInBlocks * mBlockWidth);
+
+ // constrain by smaller dimension limit
+ if (height > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int) Math.ceil(mAspectRatioRange.getLower().doubleValue()
+ * height),
+ (int) (mAspectRatioRange.getUpper().doubleValue() * height));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // height is not supported because there are no suitable widths
+ Log.v(TAG, "could not get supported widths for " + height);
+ throw new IllegalArgumentException("unsupported height");
}
- String subKey = key.substring(prefix.length());
- if (subKey.equals("none") && ret.size() == 0) {
- // This means that component knowingly did not publish performance points.
- // This is different from when the component forgot to publish performance
- // points.
- return Collections.unmodifiableList(ret);
+ }
+
+ public Range<Integer> getSupportedHeightsFor(int width) {
+ try {
+ Range<Integer> range = mHeightRange;
+ if (!mWidthRange.contains(width)
+ || (width % mWidthAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported width");
+ }
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+
+ // constrain by block count and by block aspect ratio
+ final int minHeightInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
+ (int) Math.ceil(widthInBlocks
+ / mBlockAspectRatioRange.getUpper().doubleValue()));
+ final int maxHeightInBlocks = Math.min(
+ mBlockCountRange.getUpper() / widthInBlocks,
+ (int) (widthInBlocks
+ / mBlockAspectRatioRange.getLower().doubleValue()));
+ range = range.intersect(
+ (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
+ maxHeightInBlocks * mBlockHeight);
+
+ // constrain by smaller dimension limit
+ if (width > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int) Math.ceil(width
+ / mAspectRatioRange.getUpper().doubleValue()),
+ (int) (width / mAspectRatioRange.getLower().doubleValue()));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // width is not supported because there are no suitable heights
+ Log.v(TAG, "could not get supported heights for " + width);
+ throw new IllegalArgumentException("unsupported width");
}
- String[] temp = key.split("-");
- if (temp.length != 4) {
- continue;
+ }
+
+ public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+ Range<Integer> range = mHeightRange;
+ if (!supports(width, height, null)) {
+ throw new IllegalArgumentException("unsupported size");
}
- String sizeStr = temp[2];
- Size size = Utils.parseSize(sizeStr, null);
- if (size == null || size.getWidth() * size.getHeight() <= 0) {
- continue;
+ final int blockCount =
+ Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+
+ return Range.create(
+ Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
+ (double) mFrameRateRange.getLower()),
+ Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
+ (double) mFrameRateRange.getUpper()));
+ }
+
+ private int getBlockCount(int width, int height) {
+ return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+ }
+
+ @NonNull
+ private Size findClosestSize(int width, int height) {
+ int targetBlockCount = getBlockCount(width, height);
+ Size closestSize = null;
+ int minDiff = Integer.MAX_VALUE;
+ for (Size size : mMeasuredFrameRates.keySet()) {
+ int diff = Math.abs(targetBlockCount -
+ getBlockCount(size.getWidth(), size.getHeight()));
+ if (diff < minDiff) {
+ minDiff = diff;
+ closestSize = size;
+ }
}
- Range<Long> range = Utils.parseLongRange(map.get(key), null);
- if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
- continue;
+ return closestSize;
+ }
+
+ private Range<Double> estimateFrameRatesFor(int width, int height) {
+ Size size = findClosestSize(width, height);
+ Range<Long> range = mMeasuredFrameRates.get(size);
+ Double ratio = getBlockCount(size.getWidth(), size.getHeight())
+ / (double)Math.max(getBlockCount(width, height), 1);
+ return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
+ }
+
+ /** @throws IllegalArgumentException if the video size is not supported. */
+ @Nullable
+ public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+ if (!supports(width, height, null)) {
+ throw new IllegalArgumentException("unsupported size");
}
- PerformancePoint given = new PerformancePoint(
- size.getWidth(), size.getHeight(), range.getLower().intValue(),
- range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
- PerformancePoint rotated = new PerformancePoint(
- size.getHeight(), size.getWidth(), range.getLower().intValue(),
- range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
- ret.add(given);
- if (!given.covers(rotated)) {
- ret.add(rotated);
+
+ if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
+ Log.w(TAG, "Codec did not publish any measurement data.");
+ return null;
}
+
+ return estimateFrameRatesFor(width, height);
}
- // check if the component specified no performance point indication
- if (ret.size() == 0) {
- return null;
+ @Nullable
+ public List<PerformancePoint> getSupportedPerformancePoints() {
+ return mPerformancePoints;
}
- // sort reversed by area first, then by frame rate
- ret.sort((a, b) ->
- -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
- (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
- (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
- (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
- (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
- (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
+ public boolean areSizeAndRateSupported(
+ int width, int height, double frameRate) {
+ return supports(width, height, frameRate);
+ }
- return Collections.unmodifiableList(ret);
- }
+ public boolean isSizeSupported(int width, int height) {
+ return supports(width, height, null);
+ }
+
+ private boolean supports(Integer width, Integer height, Number rate) {
+ boolean ok = true;
- private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
- Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
- final String prefix = "measured-frame-rate-";
- Set<String> keys = map.keySet();
- for (String key : keys) {
- // looking for: measured-frame-rate-WIDTHxHEIGHT-range
- if (!key.startsWith(prefix)) {
- continue;
+ if (ok && width != null) {
+ ok = mWidthRange.contains(width)
+ && (width % mWidthAlignment == 0);
}
- String subKey = key.substring(prefix.length());
- String[] temp = key.split("-");
- if (temp.length != 5) {
- continue;
+ if (ok && height != null) {
+ ok = mHeightRange.contains(height)
+ && (height % mHeightAlignment == 0);
}
- String sizeStr = temp[3];
- Size size = Utils.parseSize(sizeStr, null);
- if (size == null || size.getWidth() * size.getHeight() <= 0) {
- continue;
+ if (ok && rate != null) {
+ ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
}
- Range<Long> range = Utils.parseLongRange(map.get(key), null);
- if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
- continue;
+ if (ok && height != null && width != null) {
+ ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+ final int blockCount = widthInBlocks * heightInBlocks;
+ ok = ok && mBlockCountRange.contains(blockCount)
+ && mBlockAspectRatioRange.contains(
+ new Rational(widthInBlocks, heightInBlocks))
+ && mAspectRatioRange.contains(new Rational(width, height));
+ if (ok && rate != null) {
+ double blocksPerSec = blockCount * rate.doubleValue();
+ ok = mBlocksPerSecondRange.contains(
+ Utils.longRangeFor(blocksPerSec));
+ }
}
- ret.put(size, range);
+ return ok;
}
- return ret;
- }
- private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
- Pair<Size, Size> range = Utils.parseSizeRange(o);
- if (range != null) {
- try {
- return Pair.create(
- Range.create(range.first.getWidth(), range.second.getWidth()),
- Range.create(range.first.getHeight(), range.second.getHeight()));
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "could not parse size range '" + o + "'");
+ /**
+ * @hide
+ * @throws java.lang.ClassCastException */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+ Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+ Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
+
+ if (!supports(width, height, rate)) {
+ return false;
+ }
+
+ if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
+ return false;
}
+
+ // we ignore color-format for now as it is not reliably reported by codec
+ return true;
}
- return null;
- }
- /** @hide */
- public static int equivalentVP9Level(MediaFormat info) {
- final Map<String, Object> map = info.getMap();
-
- Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
- int BS = blockSize.getWidth() * blockSize.getHeight();
-
- Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
- int FS = counts == null ? 0 : BS * counts.getUpper();
-
- Range<Long> blockRates =
- Utils.parseLongRange(map.get("blocks-per-second-range"), null);
- long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
-
- Pair<Range<Integer>, Range<Integer>> dimensionRanges =
- parseWidthHeightRanges(map.get("size-range"));
- int D = dimensionRanges == null ? 0 : Math.max(
- dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
-
- Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
- int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
-
- if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512)
- return CodecProfileLevel.VP9Level1;
- if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768)
- return CodecProfileLevel.VP9Level11;
- if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960)
- return CodecProfileLevel.VP9Level2;
- if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344)
- return CodecProfileLevel.VP9Level21;
- if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048)
- return CodecProfileLevel.VP9Level3;
- if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752)
- return CodecProfileLevel.VP9Level31;
- if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160)
- return CodecProfileLevel.VP9Level4;
- if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160)
- return CodecProfileLevel.VP9Level41;
- if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384)
- return CodecProfileLevel.VP9Level5;
- if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384)
- return CodecProfileLevel.VP9Level51;
- if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384)
- return CodecProfileLevel.VP9Level52;
- if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
- return CodecProfileLevel.VP9Level6;
- if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
- return CodecProfileLevel.VP9Level61;
- if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
- return CodecProfileLevel.VP9Level62;
- // returning largest level
- return CodecProfileLevel.VP9Level62;
- }
+ /** @hide */
+ public Size getBlockSize() {
+ return new Size(mBlockWidth, mBlockHeight);
+ }
- private void parseFromInfo(MediaFormat info) {
- final Map<String, Object> map = info.getMap();
- Size blockSize = new Size(mBlockWidth, mBlockHeight);
- Size alignment = new Size(mWidthAlignment, mHeightAlignment);
- Range<Integer> counts = null, widths = null, heights = null;
- Range<Integer> frameRates = null, bitRates = null;
- Range<Long> blockRates = null;
- Range<Rational> ratios = null, blockRatios = null;
-
- blockSize = Utils.parseSize(map.get("block-size"), blockSize);
- alignment = Utils.parseSize(map.get("alignment"), alignment);
- counts = Utils.parseIntRange(map.get("block-count-range"), null);
- blockRates =
- Utils.parseLongRange(map.get("blocks-per-second-range"), null);
- mMeasuredFrameRates = getMeasuredFrameRates(map);
- mPerformancePoints = getPerformancePoints(map);
- Pair<Range<Integer>, Range<Integer>> sizeRanges =
- parseWidthHeightRanges(map.get("size-range"));
- if (sizeRanges != null) {
- widths = sizeRanges.first;
- heights = sizeRanges.second;
- }
- // for now this just means using the smaller max size as 2nd
- // upper limit.
- // for now we are keeping the profile specific "width/height
- // in macroblocks" limits.
- if (map.containsKey("feature-can-swap-width-height")) {
- if (widths != null) {
- mSmallerDimensionUpperLimit =
- Math.min(widths.getUpper(), heights.getUpper());
- widths = heights = widths.extend(heights);
- } else {
- Log.w(TAG, "feature can-swap-width-height is best used with size-range");
- mSmallerDimensionUpperLimit =
- Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
- mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
- }
+ /** @hide */
+ public Range<Integer> getBlockCountRange() {
+ return mBlockCountRange;
}
- ratios = Utils.parseRationalRange(
- map.get("block-aspect-ratio-range"), null);
- blockRatios = Utils.parseRationalRange(
- map.get("pixel-aspect-ratio-range"), null);
- frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
- if (frameRates != null) {
- try {
- frameRates = frameRates.intersect(FRAME_RATE_RANGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "frame rate range (" + frameRates
- + ") is out of limits: " + FRAME_RATE_RANGE);
- frameRates = null;
- }
+ /** @hide */
+ public Range<Long> getBlocksPerSecondRange() {
+ return mBlocksPerSecondRange;
}
- bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
- if (bitRates != null) {
- try {
- bitRates = bitRates.intersect(BITRATE_RANGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "bitrate range (" + bitRates
- + ") is out of limits: " + BITRATE_RANGE);
- bitRates = null;
- }
+
+ /** @hide */
+ public Range<Rational> getAspectRatioRange(boolean blocks) {
+ return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
}
- checkPowerOfTwo(
- blockSize.getWidth(), "block-size width must be power of two");
- checkPowerOfTwo(
- blockSize.getHeight(), "block-size height must be power of two");
+ private void initWithPlatformLimits() {
+ mBitrateRange = BITRATE_RANGE;
- checkPowerOfTwo(
- alignment.getWidth(), "alignment width must be power of two");
- checkPowerOfTwo(
- alignment.getHeight(), "alignment height must be power of two");
+ mWidthRange = getSizeRange();
+ mHeightRange = getSizeRange();
+ mFrameRateRange = FRAME_RATE_RANGE;
- // update block-size and alignment
- applyMacroBlockLimits(
- Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
- Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
- alignment.getWidth(), alignment.getHeight());
+ mHorizontalBlockRange = getSizeRange();
+ mVerticalBlockRange = getSizeRange();
- if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
- // codec supports profiles that we don't know.
- // Use supplied values clipped to platform limits
- if (widths != null) {
- mWidthRange = getSizeRange().intersect(widths);
- }
- if (heights != null) {
- mHeightRange = getSizeRange().intersect(heights);
- }
- if (counts != null) {
- mBlockCountRange = POSITIVE_INTEGERS.intersect(
- Utils.factorRange(counts, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
+ // full positive ranges are supported as these get calculated
+ mBlockCountRange = POSITIVE_INTEGERS;
+ mBlocksPerSecondRange = POSITIVE_LONGS;
+
+ mBlockAspectRatioRange = POSITIVE_RATIONALS;
+ mAspectRatioRange = POSITIVE_RATIONALS;
+
+ mWidthAlignment = 1;
+ mHeightAlignment = 1;
+ mBlockWidth = 1;
+ mBlockHeight = 1;
+ mSmallerDimensionUpperLimit = getSizeRange().getUpper();
+ }
+
+ private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
+ Vector<PerformancePoint> ret = new Vector<>();
+ final String prefix = "performance-point-";
+ Set<String> keys = map.keySet();
+ for (String key : keys) {
+ // looking for: performance-point-WIDTHxHEIGHT-range
+ if (!key.startsWith(prefix)) {
+ continue;
+ }
+ String subKey = key.substring(prefix.length());
+ if (subKey.equals("none") && ret.size() == 0) {
+ // This means that component knowingly did not publish performance points.
+ // This is different from when the component forgot to publish performance
+ // points.
+ return Collections.unmodifiableList(ret);
+ }
+ String[] temp = key.split("-");
+ if (temp.length != 4) {
+ continue;
+ }
+ String sizeStr = temp[2];
+ Size size = Utils.parseSize(sizeStr, null);
+ if (size == null || size.getWidth() * size.getHeight() <= 0) {
+ continue;
+ }
+ Range<Long> range = Utils.parseLongRange(map.get(key), null);
+ if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+ continue;
+ }
+ PerformancePoint given = new PerformancePoint(
+ size.getWidth(), size.getHeight(), range.getLower().intValue(),
+ range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+ PerformancePoint rotated = new PerformancePoint(
+ size.getHeight(), size.getWidth(), range.getLower().intValue(),
+ range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+ ret.add(given);
+ if (!given.covers(rotated)) {
+ ret.add(rotated);
+ }
}
- if (blockRates != null) {
- mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
- Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
+
+ // check if the component specified no performance point indication
+ if (ret.size() == 0) {
+ return null;
}
- if (blockRatios != null) {
- mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
- Utils.scaleRange(blockRatios,
- mBlockHeight / blockSize.getHeight(),
- mBlockWidth / blockSize.getWidth()));
+
+ // sort reversed by area first, then by frame rate
+ ret.sort((a, b) ->
+ -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
+ (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
+ (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
+ (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
+ (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
+ (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
+
+ return Collections.unmodifiableList(ret);
+ }
+
+ private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
+ Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
+ final String prefix = "measured-frame-rate-";
+ Set<String> keys = map.keySet();
+ for (String key : keys) {
+ // looking for: measured-frame-rate-WIDTHxHEIGHT-range
+ if (!key.startsWith(prefix)) {
+ continue;
+ }
+ String subKey = key.substring(prefix.length());
+ String[] temp = key.split("-");
+ if (temp.length != 5) {
+ continue;
+ }
+ String sizeStr = temp[3];
+ Size size = Utils.parseSize(sizeStr, null);
+ if (size == null || size.getWidth() * size.getHeight() <= 0) {
+ continue;
+ }
+ Range<Long> range = Utils.parseLongRange(map.get(key), null);
+ if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+ continue;
+ }
+ ret.put(size, range);
}
- if (ratios != null) {
- mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+ return ret;
+ }
+
+ private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
+ Pair<Size, Size> range = Utils.parseSizeRange(o);
+ if (range != null) {
+ try {
+ return Pair.create(
+ Range.create(range.first.getWidth(), range.second.getWidth()),
+ Range.create(range.first.getHeight(), range.second.getHeight()));
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "could not parse size range '" + o + "'");
+ }
}
- if (frameRates != null) {
- mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
+ return null;
+ }
+
+ /** @hide */
+ public static int equivalentVP9Level(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+
+ Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
+ int BS = blockSize.getWidth() * blockSize.getHeight();
+
+ Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ int FS = counts == null ? 0 : BS * counts.getUpper();
+
+ Range<Long> blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
+
+ Pair<Range<Integer>, Range<Integer>> dimensionRanges =
+ parseWidthHeightRanges(map.get("size-range"));
+ int D = dimensionRanges == null ? 0 : Math.max(
+ dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
+
+ Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+ int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
+
+ if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512)
+ return CodecProfileLevel.VP9Level1;
+ if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768)
+ return CodecProfileLevel.VP9Level11;
+ if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960)
+ return CodecProfileLevel.VP9Level2;
+ if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344)
+ return CodecProfileLevel.VP9Level21;
+ if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048)
+ return CodecProfileLevel.VP9Level3;
+ if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752)
+ return CodecProfileLevel.VP9Level31;
+ if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160)
+ return CodecProfileLevel.VP9Level4;
+ if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160)
+ return CodecProfileLevel.VP9Level41;
+ if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384)
+ return CodecProfileLevel.VP9Level5;
+ if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384)
+ return CodecProfileLevel.VP9Level51;
+ if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384)
+ return CodecProfileLevel.VP9Level52;
+ if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
+ return CodecProfileLevel.VP9Level6;
+ if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
+ return CodecProfileLevel.VP9Level61;
+ if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
+ return CodecProfileLevel.VP9Level62;
+ // returning largest level
+ return CodecProfileLevel.VP9Level62;
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ Size blockSize = new Size(mBlockWidth, mBlockHeight);
+ Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+ Range<Integer> counts = null, widths = null, heights = null;
+ Range<Integer> frameRates = null, bitRates = null;
+ Range<Long> blockRates = null;
+ Range<Rational> ratios = null, blockRatios = null;
+
+ blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+ alignment = Utils.parseSize(map.get("alignment"), alignment);
+ counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ mMeasuredFrameRates = getMeasuredFrameRates(map);
+ mPerformancePoints = getPerformancePoints(map);
+ Pair<Range<Integer>, Range<Integer>> sizeRanges =
+ parseWidthHeightRanges(map.get("size-range"));
+ if (sizeRanges != null) {
+ widths = sizeRanges.first;
+ heights = sizeRanges.second;
}
- if (bitRates != null) {
- // only allow bitrate override if unsupported profiles were encountered
- if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
- mBitrateRange = BITRATE_RANGE.intersect(bitRates);
+ // for now this just means using the smaller max size as 2nd
+ // upper limit.
+ // for now we are keeping the profile specific "width/height
+ // in macroblocks" limits.
+ if (map.containsKey("feature-can-swap-width-height")) {
+ if (widths != null) {
+ mSmallerDimensionUpperLimit =
+ Math.min(widths.getUpper(), heights.getUpper());
+ widths = heights = widths.extend(heights);
} else {
- mBitrateRange = mBitrateRange.intersect(bitRates);
+ Log.w(TAG, "feature can-swap-width-height is best used with size-range");
+ mSmallerDimensionUpperLimit =
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
+ mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
}
}
- } else {
- // no unsupported profile/levels, so restrict values to known limits
- if (widths != null) {
- mWidthRange = mWidthRange.intersect(widths);
- }
- if (heights != null) {
- mHeightRange = mHeightRange.intersect(heights);
- }
- if (counts != null) {
- mBlockCountRange = mBlockCountRange.intersect(
- Utils.factorRange(counts, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRates != null) {
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
- Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRatios != null) {
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
- Utils.scaleRange(blockRatios,
- mBlockHeight / blockSize.getHeight(),
- mBlockWidth / blockSize.getWidth()));
- }
- if (ratios != null) {
- mAspectRatioRange = mAspectRatioRange.intersect(ratios);
- }
+
+ ratios = Utils.parseRationalRange(
+ map.get("block-aspect-ratio-range"), null);
+ blockRatios = Utils.parseRationalRange(
+ map.get("pixel-aspect-ratio-range"), null);
+ frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
if (frameRates != null) {
- mFrameRateRange = mFrameRateRange.intersect(frameRates);
+ try {
+ frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "frame rate range (" + frameRates
+ + ") is out of limits: " + FRAME_RATE_RANGE);
+ frameRates = null;
+ }
}
+ bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
if (bitRates != null) {
- mBitrateRange = mBitrateRange.intersect(bitRates);
+ try {
+ bitRates = bitRates.intersect(BITRATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "bitrate range (" + bitRates
+ + ") is out of limits: " + BITRATE_RANGE);
+ bitRates = null;
+ }
}
- }
- updateLimits();
- }
- private void applyBlockLimits(
- int blockWidth, int blockHeight,
- Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
- checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
- checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
-
- final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
- final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
-
- // factor will always be a power-of-2
- int factor =
- newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
- if (factor != 1) {
- mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
- mBlocksPerSecondRange = Utils.factorRange(
- mBlocksPerSecondRange, factor);
- mBlockAspectRatioRange = Utils.scaleRange(
- mBlockAspectRatioRange,
- newBlockHeight / mBlockHeight,
- newBlockWidth / mBlockWidth);
- mHorizontalBlockRange = Utils.factorRange(
- mHorizontalBlockRange, newBlockWidth / mBlockWidth);
- mVerticalBlockRange = Utils.factorRange(
- mVerticalBlockRange, newBlockHeight / mBlockHeight);
- }
- factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
- if (factor != 1) {
- counts = Utils.factorRange(counts, factor);
- rates = Utils.factorRange(rates, factor);
- ratios = Utils.scaleRange(
- ratios, newBlockHeight / blockHeight,
- newBlockWidth / blockWidth);
- }
- mBlockCountRange = mBlockCountRange.intersect(counts);
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
- mBlockWidth = newBlockWidth;
- mBlockHeight = newBlockHeight;
- }
+ checkPowerOfTwo(
+ blockSize.getWidth(), "block-size width must be power of two");
+ checkPowerOfTwo(
+ blockSize.getHeight(), "block-size height must be power of two");
- private void applyAlignment(int widthAlignment, int heightAlignment) {
- checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
- checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+ checkPowerOfTwo(
+ alignment.getWidth(), "alignment width must be power of two");
+ checkPowerOfTwo(
+ alignment.getHeight(), "alignment height must be power of two");
- if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
- // maintain assumption that 0 < alignment <= block-size
- applyBlockLimits(
- Math.max(widthAlignment, mBlockWidth),
- Math.max(heightAlignment, mBlockHeight),
- POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+ // update block-size and alignment
+ applyMacroBlockLimits(
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+ alignment.getWidth(), alignment.getHeight());
+
+ if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
+ // codec supports profiles that we don't know.
+ // Use supplied values clipped to platform limits
+ if (widths != null) {
+ mWidthRange = getSizeRange().intersect(widths);
+ }
+ if (heights != null) {
+ mHeightRange = getSizeRange().intersect(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = POSITIVE_INTEGERS.intersect(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
+ }
+ if (bitRates != null) {
+ // only allow bitrate override if unsupported profiles were encountered
+ if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+ mBitrateRange = BITRATE_RANGE.intersect(bitRates);
+ } else {
+ mBitrateRange = mBitrateRange.intersect(bitRates);
+ }
+ }
+ } else {
+ // no unsupported profile/levels, so restrict values to known limits
+ if (widths != null) {
+ mWidthRange = mWidthRange.intersect(widths);
+ }
+ if (heights != null) {
+ mHeightRange = mHeightRange.intersect(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = mBlockCountRange.intersect(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = mAspectRatioRange.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = mFrameRateRange.intersect(frameRates);
+ }
+ if (bitRates != null) {
+ mBitrateRange = mBitrateRange.intersect(bitRates);
+ }
+ }
+ updateLimits();
}
- mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
- mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+ private void applyBlockLimits(
+ int blockWidth, int blockHeight,
+ Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+ checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+ checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+ final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+ final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+ // factor will always be a power-of-2
+ int factor =
+ newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+ if (factor != 1) {
+ mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+ mBlocksPerSecondRange = Utils.factorRange(
+ mBlocksPerSecondRange, factor);
+ mBlockAspectRatioRange = Utils.scaleRange(
+ mBlockAspectRatioRange,
+ newBlockHeight / mBlockHeight,
+ newBlockWidth / mBlockWidth);
+ mHorizontalBlockRange = Utils.factorRange(
+ mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+ mVerticalBlockRange = Utils.factorRange(
+ mVerticalBlockRange, newBlockHeight / mBlockHeight);
+ }
+ factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+ if (factor != 1) {
+ counts = Utils.factorRange(counts, factor);
+ rates = Utils.factorRange(rates, factor);
+ ratios = Utils.scaleRange(
+ ratios, newBlockHeight / blockHeight,
+ newBlockWidth / blockWidth);
+ }
+ mBlockCountRange = mBlockCountRange.intersect(counts);
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+ mBlockWidth = newBlockWidth;
+ mBlockHeight = newBlockHeight;
+ }
- mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
- mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
- }
+ private void applyAlignment(int widthAlignment, int heightAlignment) {
+ checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+ checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
- private void updateLimits() {
- // pixels -> blocks <- counts
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Utils.factorRange(mWidthRange, mBlockWidth));
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Utils.factorRange(mHeightRange, mBlockHeight));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
- mBlockCountRange = mBlockCountRange.intersect(
- Range.create(
- mHorizontalBlockRange.getLower()
- * mVerticalBlockRange.getLower(),
- mHorizontalBlockRange.getUpper()
- * mVerticalBlockRange.getUpper()));
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
- new Rational(
- mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
- new Rational(
- mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
-
- // blocks -> pixels
- mWidthRange = mWidthRange.intersect(
- (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
- mHorizontalBlockRange.getUpper() * mBlockWidth);
- mHeightRange = mHeightRange.intersect(
- (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
- mVerticalBlockRange.getUpper() * mBlockHeight);
- mAspectRatioRange = mAspectRatioRange.intersect(
- new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
- new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
-
- mSmallerDimensionUpperLimit = Math.min(
- mSmallerDimensionUpperLimit,
- Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
-
- // blocks -> rate
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
- mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
- mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
- mFrameRateRange = mFrameRateRange.intersect(
- (int)(mBlocksPerSecondRange.getLower()
- / mBlockCountRange.getUpper()),
- (int)(mBlocksPerSecondRange.getUpper()
- / (double)mBlockCountRange.getLower()));
- }
+ if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+ // maintain assumption that 0 < alignment <= block-size
+ applyBlockLimits(
+ Math.max(widthAlignment, mBlockWidth),
+ Math.max(heightAlignment, mBlockHeight),
+ POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+ }
- private void applyMacroBlockLimits(
- int maxHorizontalBlocks, int maxVerticalBlocks,
- int maxBlocks, long maxBlocksPerSecond,
- int blockWidth, int blockHeight,
- int widthAlignment, int heightAlignment) {
- applyMacroBlockLimits(
- 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
- maxHorizontalBlocks, maxVerticalBlocks,
- maxBlocks, maxBlocksPerSecond,
- blockWidth, blockHeight, widthAlignment, heightAlignment);
- }
+ mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+ mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
- private void applyMacroBlockLimits(
- int minHorizontalBlocks, int minVerticalBlocks,
- int maxHorizontalBlocks, int maxVerticalBlocks,
- int maxBlocks, long maxBlocksPerSecond,
- int blockWidth, int blockHeight,
- int widthAlignment, int heightAlignment) {
- applyAlignment(widthAlignment, heightAlignment);
- applyBlockLimits(
- blockWidth, blockHeight, Range.create(1, maxBlocks),
- Range.create(1L, maxBlocksPerSecond),
- Range.create(
- new Rational(1, maxVerticalBlocks),
- new Rational(maxHorizontalBlocks, 1)));
- mHorizontalBlockRange =
- mHorizontalBlockRange.intersect(
- Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
- maxHorizontalBlocks / (mBlockWidth / blockWidth));
- mVerticalBlockRange =
- mVerticalBlockRange.intersect(
- Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
- maxVerticalBlocks / (mBlockHeight / blockHeight));
- }
+ mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+ mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+ }
- private void applyLevelLimits() {
- long maxBlocksPerSecond = 0;
- int maxBlocks = 0;
- int maxBps = 0;
- int maxDPBBlocks = 0;
-
- int errors = ERROR_NONE_SUPPORTED;
- CodecProfileLevel[] profileLevels = mParent.profileLevels;
- String mime = mParent.getMimeType();
-
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- maxDPBBlocks = 396;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, DPB = 0;
- boolean supported = true;
- switch (profileLevel.level) {
- case CodecProfileLevel.AVCLevel1:
- MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
- case CodecProfileLevel.AVCLevel1b:
- MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
- case CodecProfileLevel.AVCLevel11:
- MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
- case CodecProfileLevel.AVCLevel12:
- MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel13:
- MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel2:
- MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel21:
- MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
- case CodecProfileLevel.AVCLevel22:
- MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
- case CodecProfileLevel.AVCLevel3:
- MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
- case CodecProfileLevel.AVCLevel31:
- MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
- case CodecProfileLevel.AVCLevel32:
- MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
- case CodecProfileLevel.AVCLevel4:
- MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
- case CodecProfileLevel.AVCLevel41:
- MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
- case CodecProfileLevel.AVCLevel42:
- MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
- case CodecProfileLevel.AVCLevel5:
- MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
- case CodecProfileLevel.AVCLevel51:
- MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
- case CodecProfileLevel.AVCLevel52:
- MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
- case CodecProfileLevel.AVCLevel6:
- MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break;
- case CodecProfileLevel.AVCLevel61:
- MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break;
- case CodecProfileLevel.AVCLevel62:
- MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.AVCProfileConstrainedHigh:
- case CodecProfileLevel.AVCProfileHigh:
- BR *= 1250; break;
- case CodecProfileLevel.AVCProfileHigh10:
- BR *= 3000; break;
- case CodecProfileLevel.AVCProfileExtended:
- case CodecProfileLevel.AVCProfileHigh422:
- case CodecProfileLevel.AVCProfileHigh444:
- Log.w(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- // fall through - treat as base profile
- case CodecProfileLevel.AVCProfileConstrainedBaseline:
- case CodecProfileLevel.AVCProfileBaseline:
- case CodecProfileLevel.AVCProfileMain:
- BR *= 1000; break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- BR *= 1000;
- }
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR, maxBps);
- maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
- }
+ private void updateLimits() {
+ // pixels -> blocks <- counts
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Utils.factorRange(mWidthRange, mBlockWidth));
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Utils.factorRange(mHeightRange, mBlockHeight));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
+ mBlockCountRange = mBlockCountRange.intersect(
+ Range.create(
+ mHorizontalBlockRange.getLower()
+ * mVerticalBlockRange.getLower(),
+ mHorizontalBlockRange.getUpper()
+ * mVerticalBlockRange.getUpper()));
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ new Rational(
+ mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+ new Rational(
+ mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+ // blocks -> pixels
+ mWidthRange = mWidthRange.intersect(
+ (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+ mHorizontalBlockRange.getUpper() * mBlockWidth);
+ mHeightRange = mHeightRange.intersect(
+ (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+ mVerticalBlockRange.getUpper() * mBlockHeight);
+ mAspectRatioRange = mAspectRatioRange.intersect(
+ new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+ new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+ mSmallerDimensionUpperLimit = Math.min(
+ mSmallerDimensionUpperLimit,
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+ // blocks -> rate
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+ mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+ mFrameRateRange = mFrameRateRange.intersect(
+ (int)(mBlocksPerSecondRange.getLower()
+ / mBlockCountRange.getUpper()),
+ (int)(mBlocksPerSecondRange.getUpper()
+ / (double)mBlockCountRange.getLower()));
+ }
- int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ private void applyMacroBlockLimits(
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
+ 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
+ maxHorizontalBlocks, maxVerticalBlocks,
maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
- boolean supported = true;
- switch (profileLevel.profile) {
- case CodecProfileLevel.MPEG2ProfileSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG2LevelML:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG2ProfileMain:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG2LevelLL:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break;
- case CodecProfileLevel.MPEG2LevelML:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
- case CodecProfileLevel.MPEG2LevelH14:
- FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break;
- case CodecProfileLevel.MPEG2LevelHL:
- FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break;
- case CodecProfileLevel.MPEG2LevelHP:
- FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG2Profile422:
- case CodecProfileLevel.MPEG2ProfileSNR:
- case CodecProfileLevel.MPEG2ProfileSpatial:
- case CodecProfileLevel.MPEG2ProfileHigh:
- Log.i(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ blockWidth, blockHeight, widthAlignment, heightAlignment);
+ }
+
+ private void applyMacroBlockLimits(
+ int minHorizontalBlocks, int minVerticalBlocks,
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
+ applyAlignment(widthAlignment, heightAlignment);
+ applyBlockLimits(
+ blockWidth, blockHeight, Range.create(1, maxBlocks),
+ Range.create(1L, maxBlocksPerSecond),
+ Range.create(
+ new Rational(1, maxVerticalBlocks),
+ new Rational(maxHorizontalBlocks, 1)));
+ mHorizontalBlockRange =
+ mHorizontalBlockRange.intersect(
+ Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
+ maxHorizontalBlocks / (mBlockWidth / blockWidth));
+ mVerticalBlockRange =
+ mVerticalBlockRange.intersect(
+ Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
+ maxVerticalBlocks / (mBlockHeight / blockHeight));
+ }
+
+ private void applyLevelLimits() {
+ long maxBlocksPerSecond = 0;
+ int maxBlocks = 0;
+ int maxBps = 0;
+ int maxDPBBlocks = 0;
+
+ int errors = ERROR_NONE_SUPPORTED;
+ CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
+ String mime = mParent.getMimeType();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ maxDPBBlocks = 396;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+ boolean supported = true;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.AVCLevel1:
+ MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel1b:
+ MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel11:
+ MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
+ case CodecProfileLevel.AVCLevel12:
+ MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel13:
+ MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel2:
+ MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel21:
+ MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
+ case CodecProfileLevel.AVCLevel22:
+ MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel3:
+ MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel31:
+ MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
+ case CodecProfileLevel.AVCLevel32:
+ MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
+ case CodecProfileLevel.AVCLevel4:
+ MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel41:
+ MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel42:
+ MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
+ case CodecProfileLevel.AVCLevel5:
+ MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
+ case CodecProfileLevel.AVCLevel51:
+ MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
+ case CodecProfileLevel.AVCLevel52:
+ MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
+ case CodecProfileLevel.AVCLevel6:
+ MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break;
+ case CodecProfileLevel.AVCLevel61:
+ MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break;
+ case CodecProfileLevel.AVCLevel62:
+ MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.AVCProfileConstrainedHigh:
+ case CodecProfileLevel.AVCProfileHigh:
+ BR *= 1250; break;
+ case CodecProfileLevel.AVCProfileHigh10:
+ BR *= 3000; break;
+ case CodecProfileLevel.AVCProfileExtended:
+ case CodecProfileLevel.AVCProfileHigh422:
+ case CodecProfileLevel.AVCProfileHigh444:
+ Log.w(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ // fall through - treat as base profile
+ case CodecProfileLevel.AVCProfileConstrainedBaseline:
+ case CodecProfileLevel.AVCProfileBaseline:
+ case CodecProfileLevel.AVCProfileMain:
+ BR *= 1000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ BR *= 1000;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR, maxBps);
+ maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
}
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG2ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2ProfileMain:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelLL:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break;
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
+ case CodecProfileLevel.MPEG2LevelH14:
+ FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break;
+ case CodecProfileLevel.MPEG2LevelHL:
+ FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break;
+ case CodecProfileLevel.MPEG2LevelHP:
+ FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2Profile422:
+ case CodecProfileLevel.MPEG2ProfileSNR:
+ case CodecProfileLevel.MPEG2ProfileSpatial:
+ case CodecProfileLevel.MPEG2ProfileHigh:
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
}
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
- boolean strict = false; // true: W, H and FR are individual max limits
- boolean supported = true;
- switch (profileLevel.profile) {
- case CodecProfileLevel.MPEG4ProfileSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- strict = true;
- FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level0b:
- strict = true;
- FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level4a:
- FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
- case CodecProfileLevel.MPEG4Level5:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
- case CodecProfileLevel.MPEG4Level6:
- FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
- case CodecProfileLevel.MPEG4Level3b:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break;
- case CodecProfileLevel.MPEG4Level4:
- FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
- case CodecProfileLevel.MPEG4Level5:
- FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileMain: // 2-4
- case CodecProfileLevel.MPEG4ProfileNbit: // 2
- case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
- case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
- case CodecProfileLevel.MPEG4ProfileCore: // 1-2
- case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
- case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
- case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
-
- // Studio profiles are not supported by our codecs.
-
- // Only profiles that can decode simple object types are considered.
- // The following profiles are not able to.
- case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
- case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
- case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
- case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
- Log.i(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean strict = false; // true: W, H and FR are individual max limits
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG4ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ strict = true;
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level0b:
+ strict = true;
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level4a:
+ FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
+ case CodecProfileLevel.MPEG4Level6:
+ FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
+ case CodecProfileLevel.MPEG4Level3b:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break;
+ case CodecProfileLevel.MPEG4Level4:
+ FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileMain: // 2-4
+ case CodecProfileLevel.MPEG4ProfileNbit: // 2
+ case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCore: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
+ case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
+ case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
+
+ // Studio profiles are not supported by our codecs.
+
+ // Only profiles that can decode simple object types are considered.
+ // The following profiles are not able to.
+ case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
+ case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
+ case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ if (strict) {
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ } else {
+ // assuming max 60 fps frame rate and 1:2 aspect ratio
+ int maxDim = (int)Math.sqrt(FS * 2);
+ maxWidth = Math.max(maxDim, maxWidth);
+ maxHeight = Math.max(maxDim, maxHeight);
+ maxRate = Math.max(Math.max(FR, 60), maxRate);
+ }
}
- if (supported) {
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ int minWidth = maxWidth, minHeight = maxHeight;
+ int minAlignment = 16;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
+ boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
+ switch (profileLevel.level) {
+ case CodecProfileLevel.H263Level10:
+ strict = true; // only supports sQCIF & QCIF
+ FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level20:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break;
+ case CodecProfileLevel.H263Level30:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level40:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level45:
+ // only implies level 10 support
+ strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
+ || profileLevel.profile ==
+ CodecProfileLevel.H263ProfileBackwardCompatible;
+ if (!strict) {
+ minW = 1; minH = 1; minAlignment = 4;
+ }
+ FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level50:
+ // only supports 50fps for H > 15
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level60:
+ // only supports 50fps for H > 15
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level70:
+ // only supports 50fps for H > 30
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+ + "/" + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.H263ProfileBackwardCompatible:
+ case CodecProfileLevel.H263ProfileBaseline:
+ case CodecProfileLevel.H263ProfileH320Coding:
+ case CodecProfileLevel.H263ProfileHighCompression:
+ case CodecProfileLevel.H263ProfileHighLatency:
+ case CodecProfileLevel.H263ProfileInterlace:
+ case CodecProfileLevel.H263ProfileInternet:
+ case CodecProfileLevel.H263ProfileISWV2:
+ case CodecProfileLevel.H263ProfileISWV3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (strict) {
+ // Strict levels define sub-QCIF min size and enumerated sizes. We
+ // cannot express support for "only sQCIF & QCIF (& CIF)" using
+ // VideoCapabilities but we can express "only QCIF (& CIF)", so set
+ // minimume size at QCIF.minW = 8; minH = 6;
+ minW = 11; minH = 9;
+ } else {
+ // any support for non-strict levels (including unrecognized profiles or
+ // levels) allow custom frame size support beyond supported limits
+ // (other than bitrate)
+ mAllowMbOverride = true;
+ }
errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- if (strict) {
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(W * H, maxBlocks);
+ maxBps = Math.max(BR * 64000, maxBps);
maxWidth = Math.max(W, maxWidth);
maxHeight = Math.max(H, maxHeight);
maxRate = Math.max(FR, maxRate);
- } else {
- // assuming max 60 fps frame rate and 1:2 aspect ratio
- int maxDim = (int)Math.sqrt(FS * 2);
- maxWidth = Math.max(maxDim, maxWidth);
- maxHeight = Math.max(maxDim, maxHeight);
- maxRate = Math.max(Math.max(FR, 60), maxRate);
- }
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- int minWidth = maxWidth, minHeight = maxHeight;
- int minAlignment = 16;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
- boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
- switch (profileLevel.level) {
- case CodecProfileLevel.H263Level10:
- strict = true; // only supports sQCIF & QCIF
- FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level20:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break;
- case CodecProfileLevel.H263Level30:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level40:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level45:
- // only implies level 10 support
- strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
- || profileLevel.profile ==
- CodecProfileLevel.H263ProfileBackwardCompatible;
- if (!strict) {
- minW = 1; minH = 1; minAlignment = 4;
- }
- FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level50:
- // only supports 50fps for H > 15
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level60:
- // only supports 50fps for H > 15
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level70:
- // only supports 50fps for H > 30
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
- default:
- Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
- + "/" + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.H263ProfileBackwardCompatible:
- case CodecProfileLevel.H263ProfileBaseline:
- case CodecProfileLevel.H263ProfileH320Coding:
- case CodecProfileLevel.H263ProfileHighCompression:
- case CodecProfileLevel.H263ProfileHighLatency:
- case CodecProfileLevel.H263ProfileInterlace:
- case CodecProfileLevel.H263ProfileInternet:
- case CodecProfileLevel.H263ProfileISWV2:
- case CodecProfileLevel.H263ProfileISWV3:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- if (strict) {
- // Strict levels define sub-QCIF min size and enumerated sizes. We cannot
- // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities
- // but we can express "only QCIF (& CIF)", so set minimume size at QCIF.
- // minW = 8; minH = 6;
- minW = 11; minH = 9;
- } else {
- // any support for non-strict levels (including unrecognized profiles or
- // levels) allow custom frame size support beyond supported limits
- // (other than bitrate)
- mAllowMbOverride = true;
+ minWidth = Math.min(minW, minWidth);
+ minHeight = Math.min(minH, minHeight);
}
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(W * H, maxBlocks);
- maxBps = Math.max(BR * 64000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- minWidth = Math.min(minW, minWidth);
- minHeight = Math.min(minH, minHeight);
- }
- // unless we encountered custom frame size support, limit size to QCIF and CIF
- // using aspect ratio.
- if (!mAllowMbOverride) {
- mBlockAspectRatioRange =
- Range.create(new Rational(11, 9), new Rational(11, 9));
- }
- applyMacroBlockLimits(
- minWidth, minHeight,
- maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
- mFrameRateRange = Range.create(1, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
- maxBlocks = Integer.MAX_VALUE;
- maxBlocksPerSecond = Integer.MAX_VALUE;
-
- // TODO: set to 100Mbps for now, need a number for VP8
- maxBps = 100000000;
-
- // profile levels are not indicative for VPx, but verify
- // them nonetheless
- for (CodecProfileLevel profileLevel: profileLevels) {
- switch (profileLevel.level) {
- case CodecProfileLevel.VP8Level_Version0:
- case CodecProfileLevel.VP8Level_Version1:
- case CodecProfileLevel.VP8Level_Version2:
- case CodecProfileLevel.VP8Level_Version3:
- break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ // unless we encountered custom frame size support, limit size to QCIF and CIF
+ // using aspect ratio.
+ if (!mAllowMbOverride) {
+ mBlockAspectRatioRange =
+ Range.create(new Rational(11, 9), new Rational(11, 9));
}
- switch (profileLevel.profile) {
- case CodecProfileLevel.VP8ProfileMain:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ applyMacroBlockLimits(
+ minWidth, minHeight,
+ maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
+ mFrameRateRange = Range.create(1, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
+ maxBlocks = Integer.MAX_VALUE;
+ maxBlocksPerSecond = Integer.MAX_VALUE;
+
+ // TODO: set to 100Mbps for now, need a number for VP8
+ maxBps = 100000000;
+
+ // profile levels are not indicative for VPx, but verify
+ // them nonetheless
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP8Level_Version0:
+ case CodecProfileLevel.VP8Level_Version1:
+ case CodecProfileLevel.VP8Level_Version2:
+ case CodecProfileLevel.VP8Level_Version3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP8ProfileMain:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
}
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- final int blockSize = 16;
- applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
- maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
- maxBlocksPerSecond = 829440;
- maxBlocks = 36864;
- maxBps = 200000;
- int maxDim = 512;
-
- for (CodecProfileLevel profileLevel: profileLevels) {
- long SR = 0; // luma sample rate
- int FS = 0; // luma picture size
- int BR = 0; // bit rate kbps
- int D = 0; // luma dimension
- switch (profileLevel.level) {
- case CodecProfileLevel.VP9Level1:
- SR = 829440; FS = 36864; BR = 200; D = 512; break;
- case CodecProfileLevel.VP9Level11:
- SR = 2764800; FS = 73728; BR = 800; D = 768; break;
- case CodecProfileLevel.VP9Level2:
- SR = 4608000; FS = 122880; BR = 1800; D = 960; break;
- case CodecProfileLevel.VP9Level21:
- SR = 9216000; FS = 245760; BR = 3600; D = 1344; break;
- case CodecProfileLevel.VP9Level3:
- SR = 20736000; FS = 552960; BR = 7200; D = 2048; break;
- case CodecProfileLevel.VP9Level31:
- SR = 36864000; FS = 983040; BR = 12000; D = 2752; break;
- case CodecProfileLevel.VP9Level4:
- SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break;
- case CodecProfileLevel.VP9Level41:
- SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break;
- case CodecProfileLevel.VP9Level5:
- SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break;
- case CodecProfileLevel.VP9Level51:
- SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break;
- case CodecProfileLevel.VP9Level52:
- SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break;
- case CodecProfileLevel.VP9Level6:
- SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break;
- case CodecProfileLevel.VP9Level61:
- SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
- case CodecProfileLevel.VP9Level62:
- SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.VP9Profile0:
- case CodecProfileLevel.VP9Profile1:
- case CodecProfileLevel.VP9Profile2:
- case CodecProfileLevel.VP9Profile3:
- case CodecProfileLevel.VP9Profile2HDR:
- case CodecProfileLevel.VP9Profile3HDR:
- case CodecProfileLevel.VP9Profile2HDR10Plus:
- case CodecProfileLevel.VP9Profile3HDR10Plus:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ final int blockSize = 16;
+ applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+ maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ maxBlocksPerSecond = 829440;
+ maxBlocks = 36864;
+ maxBps = 200000;
+ int maxDim = 512;
+
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ long SR = 0; // luma sample rate
+ int FS = 0; // luma picture size
+ int BR = 0; // bit rate kbps
+ int D = 0; // luma dimension
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP9Level1:
+ SR = 829440; FS = 36864; BR = 200; D = 512; break;
+ case CodecProfileLevel.VP9Level11:
+ SR = 2764800; FS = 73728; BR = 800; D = 768; break;
+ case CodecProfileLevel.VP9Level2:
+ SR = 4608000; FS = 122880; BR = 1800; D = 960; break;
+ case CodecProfileLevel.VP9Level21:
+ SR = 9216000; FS = 245760; BR = 3600; D = 1344; break;
+ case CodecProfileLevel.VP9Level3:
+ SR = 20736000; FS = 552960; BR = 7200; D = 2048; break;
+ case CodecProfileLevel.VP9Level31:
+ SR = 36864000; FS = 983040; BR = 12000; D = 2752; break;
+ case CodecProfileLevel.VP9Level4:
+ SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break;
+ case CodecProfileLevel.VP9Level41:
+ SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break;
+ case CodecProfileLevel.VP9Level5:
+ SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break;
+ case CodecProfileLevel.VP9Level51:
+ SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break;
+ case CodecProfileLevel.VP9Level52:
+ SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break;
+ case CodecProfileLevel.VP9Level6:
+ SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break;
+ case CodecProfileLevel.VP9Level61:
+ SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
+ case CodecProfileLevel.VP9Level62:
+ SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP9Profile0:
+ case CodecProfileLevel.VP9Profile1:
+ case CodecProfileLevel.VP9Profile2:
+ case CodecProfileLevel.VP9Profile3:
+ case CodecProfileLevel.VP9Profile2HDR:
+ case CodecProfileLevel.VP9Profile3HDR:
+ case CodecProfileLevel.VP9Profile2HDR10Plus:
+ case CodecProfileLevel.VP9Profile3HDR10Plus:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxDim = Math.max(D, maxDim);
}
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- maxDim = Math.max(D, maxDim);
- }
- final int blockSize = 8;
- int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
- maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
- maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+ final int blockSize = 8;
+ int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+ maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ blockSize, blockSize,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ // CTBs are at least 8x8 so use 8x8 block size
+ maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
+ maxBlocksPerSecond = maxBlocks * 15;
+ maxBps = 128000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ double FR = 0;
+ int FS = 0;
+ int BR = 0;
+ switch (profileLevel.level) {
+ /* The HEVC spec talks only in a very convoluted manner about the
+ existence of levels 1-3.1 for High tier, which could also be
+ understood as 'decoders and encoders should treat these levels
+ as if they were Main tier', so we do that. */
+ case CodecProfileLevel.HEVCMainTierLevel1:
+ case CodecProfileLevel.HEVCHighTierLevel1:
+ FR = 15; FS = 36864; BR = 128; break;
+ case CodecProfileLevel.HEVCMainTierLevel2:
+ case CodecProfileLevel.HEVCHighTierLevel2:
+ FR = 30; FS = 122880; BR = 1500; break;
+ case CodecProfileLevel.HEVCMainTierLevel21:
+ case CodecProfileLevel.HEVCHighTierLevel21:
+ FR = 30; FS = 245760; BR = 3000; break;
+ case CodecProfileLevel.HEVCMainTierLevel3:
+ case CodecProfileLevel.HEVCHighTierLevel3:
+ FR = 30; FS = 552960; BR = 6000; break;
+ case CodecProfileLevel.HEVCMainTierLevel31:
+ case CodecProfileLevel.HEVCHighTierLevel31:
+ FR = 33.75; FS = 983040; BR = 10000; break;
+ case CodecProfileLevel.HEVCMainTierLevel4:
+ FR = 30; FS = 2228224; BR = 12000; break;
+ case CodecProfileLevel.HEVCHighTierLevel4:
+ FR = 30; FS = 2228224; BR = 30000; break;
+ case CodecProfileLevel.HEVCMainTierLevel41:
+ FR = 60; FS = 2228224; BR = 20000; break;
+ case CodecProfileLevel.HEVCHighTierLevel41:
+ FR = 60; FS = 2228224; BR = 50000; break;
+ case CodecProfileLevel.HEVCMainTierLevel5:
+ FR = 30; FS = 8912896; BR = 25000; break;
+ case CodecProfileLevel.HEVCHighTierLevel5:
+ FR = 30; FS = 8912896; BR = 100000; break;
+ case CodecProfileLevel.HEVCMainTierLevel51:
+ FR = 60; FS = 8912896; BR = 40000; break;
+ case CodecProfileLevel.HEVCHighTierLevel51:
+ FR = 60; FS = 8912896; BR = 160000; break;
+ case CodecProfileLevel.HEVCMainTierLevel52:
+ FR = 120; FS = 8912896; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel52:
+ FR = 120; FS = 8912896; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel6:
+ FR = 30; FS = 35651584; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel6:
+ FR = 30; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel61:
+ FR = 60; FS = 35651584; BR = 120000; break;
+ case CodecProfileLevel.HEVCHighTierLevel61:
+ FR = 60; FS = 35651584; BR = 480000; break;
+ case CodecProfileLevel.HEVCMainTierLevel62:
+ FR = 120; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCHighTierLevel62:
+ FR = 120; FS = 35651584; BR = 800000; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.HEVCProfileMain:
+ case CodecProfileLevel.HEVCProfileMain10:
+ case CodecProfileLevel.HEVCProfileMainStill:
+ case CodecProfileLevel.HEVCProfileMain10HDR10:
+ case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- blockSize, blockSize,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
- // CTBs are at least 8x8 so use 8x8 block size
- maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
- maxBlocksPerSecond = maxBlocks * 15;
- maxBps = 128000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- double FR = 0;
- int FS = 0;
- int BR = 0;
- switch (profileLevel.level) {
- /* The HEVC spec talks only in a very convoluted manner about the
- existence of levels 1-3.1 for High tier, which could also be
- understood as 'decoders and encoders should treat these levels
- as if they were Main tier', so we do that. */
- case CodecProfileLevel.HEVCMainTierLevel1:
- case CodecProfileLevel.HEVCHighTierLevel1:
- FR = 15; FS = 36864; BR = 128; break;
- case CodecProfileLevel.HEVCMainTierLevel2:
- case CodecProfileLevel.HEVCHighTierLevel2:
- FR = 30; FS = 122880; BR = 1500; break;
- case CodecProfileLevel.HEVCMainTierLevel21:
- case CodecProfileLevel.HEVCHighTierLevel21:
- FR = 30; FS = 245760; BR = 3000; break;
- case CodecProfileLevel.HEVCMainTierLevel3:
- case CodecProfileLevel.HEVCHighTierLevel3:
- FR = 30; FS = 552960; BR = 6000; break;
- case CodecProfileLevel.HEVCMainTierLevel31:
- case CodecProfileLevel.HEVCHighTierLevel31:
- FR = 33.75; FS = 983040; BR = 10000; break;
- case CodecProfileLevel.HEVCMainTierLevel4:
- FR = 30; FS = 2228224; BR = 12000; break;
- case CodecProfileLevel.HEVCHighTierLevel4:
- FR = 30; FS = 2228224; BR = 30000; break;
- case CodecProfileLevel.HEVCMainTierLevel41:
- FR = 60; FS = 2228224; BR = 20000; break;
- case CodecProfileLevel.HEVCHighTierLevel41:
- FR = 60; FS = 2228224; BR = 50000; break;
- case CodecProfileLevel.HEVCMainTierLevel5:
- FR = 30; FS = 8912896; BR = 25000; break;
- case CodecProfileLevel.HEVCHighTierLevel5:
- FR = 30; FS = 8912896; BR = 100000; break;
- case CodecProfileLevel.HEVCMainTierLevel51:
- FR = 60; FS = 8912896; BR = 40000; break;
- case CodecProfileLevel.HEVCHighTierLevel51:
- FR = 60; FS = 8912896; BR = 160000; break;
- case CodecProfileLevel.HEVCMainTierLevel52:
- FR = 120; FS = 8912896; BR = 60000; break;
- case CodecProfileLevel.HEVCHighTierLevel52:
- FR = 120; FS = 8912896; BR = 240000; break;
- case CodecProfileLevel.HEVCMainTierLevel6:
- FR = 30; FS = 35651584; BR = 60000; break;
- case CodecProfileLevel.HEVCHighTierLevel6:
- FR = 30; FS = 35651584; BR = 240000; break;
- case CodecProfileLevel.HEVCMainTierLevel61:
- FR = 60; FS = 35651584; BR = 120000; break;
- case CodecProfileLevel.HEVCHighTierLevel61:
- FR = 60; FS = 35651584; BR = 480000; break;
- case CodecProfileLevel.HEVCMainTierLevel62:
- FR = 120; FS = 35651584; BR = 240000; break;
- case CodecProfileLevel.HEVCHighTierLevel62:
- FR = 120; FS = 35651584; BR = 800000; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+ /* DPB logic:
+ if (width * height <= FS / 4) DPB = 16;
+ else if (width * height <= FS / 2) DPB = 12;
+ else if (width * height <= FS * 0.75) DPB = 8;
+ else DPB = 6;
+ */
+
+ FS >>= 6; // convert pixels to blocks
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
}
- switch (profileLevel.profile) {
- case CodecProfileLevel.HEVCProfileMain:
- case CodecProfileLevel.HEVCProfileMain10:
- case CodecProfileLevel.HEVCProfileMainStill:
- case CodecProfileLevel.HEVCProfileMain10HDR10:
- case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ 8 /* blockWidth */, 8 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
+ maxBlocksPerSecond = 829440;
+ maxBlocks = 36864;
+ maxBps = 200000;
+ int maxDim = 512;
+
+ // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
+ // corresponding to the definitions in
+ // "AV1 Bitstream & Decoding Process Specification", Annex A
+ // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ long SR = 0; // luma sample rate
+ int FS = 0; // luma picture size
+ int BR = 0; // bit rate kbps
+ int D = 0; // luma D
+ switch (profileLevel.level) {
+ case CodecProfileLevel.AV1Level2:
+ SR = 5529600; FS = 147456; BR = 1500; D = 2048; break;
+ case CodecProfileLevel.AV1Level21:
+ case CodecProfileLevel.AV1Level22:
+ case CodecProfileLevel.AV1Level23:
+ SR = 10454400; FS = 278784; BR = 3000; D = 2816; break;
+
+ case CodecProfileLevel.AV1Level3:
+ SR = 24969600; FS = 665856; BR = 6000; D = 4352; break;
+ case CodecProfileLevel.AV1Level31:
+ case CodecProfileLevel.AV1Level32:
+ case CodecProfileLevel.AV1Level33:
+ SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break;
+
+ case CodecProfileLevel.AV1Level4:
+ SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break;
+ case CodecProfileLevel.AV1Level41:
+ case CodecProfileLevel.AV1Level42:
+ case CodecProfileLevel.AV1Level43:
+ SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break;
+
+ case CodecProfileLevel.AV1Level5:
+ SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break;
+ case CodecProfileLevel.AV1Level51:
+ SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break;
+ case CodecProfileLevel.AV1Level52:
+ SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break;
+ case CodecProfileLevel.AV1Level53:
+ SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break;
+
+ case CodecProfileLevel.AV1Level6:
+ SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break;
+ case CodecProfileLevel.AV1Level61:
+ SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
+ case CodecProfileLevel.AV1Level62:
+ SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
+ case CodecProfileLevel.AV1Level63:
+ SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
+
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.AV1ProfileMain8:
+ case CodecProfileLevel.AV1ProfileMain10:
+ case CodecProfileLevel.AV1ProfileMain10HDR10:
+ case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxDim = Math.max(D, maxDim);
}
- /* DPB logic:
- if (width * height <= FS / 4) DPB = 16;
- else if (width * height <= FS / 2) DPB = 12;
- else if (width * height <= FS * 0.75) DPB = 8;
- else DPB = 6;
- */
-
- FS >>= 6; // convert pixels to blocks
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
+ final int blockSize = 8;
+ int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+ maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ blockSize, blockSize,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else {
+ Log.w(TAG, "Unsupported mime " + mime);
+ // using minimal bitrate here. should be overridden by
+ // info from media_codecs.xml
+ maxBps = 64000;
+ errors |= ERROR_UNSUPPORTED;
}
+ mBitrateRange = Range.create(1, maxBps);
+ mParent.mError |= errors;
+ }
+ }
- int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- 8 /* blockWidth */, 8 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
- maxBlocksPerSecond = 829440;
- maxBlocks = 36864;
- maxBps = 200000;
- int maxDim = 512;
-
- // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
- // corresponding to the definitions in
- // "AV1 Bitstream & Decoding Process Specification", Annex A
- // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
- for (CodecProfileLevel profileLevel: profileLevels) {
- long SR = 0; // luma sample rate
- int FS = 0; // luma picture size
- int BR = 0; // bit rate kbps
- int D = 0; // luma D
- switch (profileLevel.level) {
- case CodecProfileLevel.AV1Level2:
- SR = 5529600; FS = 147456; BR = 1500; D = 2048; break;
- case CodecProfileLevel.AV1Level21:
- case CodecProfileLevel.AV1Level22:
- case CodecProfileLevel.AV1Level23:
- SR = 10454400; FS = 278784; BR = 3000; D = 2816; break;
-
- case CodecProfileLevel.AV1Level3:
- SR = 24969600; FS = 665856; BR = 6000; D = 4352; break;
- case CodecProfileLevel.AV1Level31:
- case CodecProfileLevel.AV1Level32:
- case CodecProfileLevel.AV1Level33:
- SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break;
-
- case CodecProfileLevel.AV1Level4:
- SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break;
- case CodecProfileLevel.AV1Level41:
- case CodecProfileLevel.AV1Level42:
- case CodecProfileLevel.AV1Level43:
- SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break;
-
- case CodecProfileLevel.AV1Level5:
- SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break;
- case CodecProfileLevel.AV1Level51:
- SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break;
- case CodecProfileLevel.AV1Level52:
- SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break;
- case CodecProfileLevel.AV1Level53:
- SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break;
-
- case CodecProfileLevel.AV1Level6:
- SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break;
- case CodecProfileLevel.AV1Level61:
- SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
- case CodecProfileLevel.AV1Level62:
- SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
- case CodecProfileLevel.AV1Level63:
- SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
-
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.AV1ProfileMain8:
- case CodecProfileLevel.AV1ProfileMain10:
- case CodecProfileLevel.AV1ProfileMain10HDR10:
- case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- maxDim = Math.max(D, maxDim);
+ /* package private */ static final class VideoCapsNativeImpl implements VideoCapsIntf {
+ private long mNativeContext; // accessed by native methods
+
+ private Range<Integer> mBitrateRange;
+ private Range<Integer> mHeightRange;
+ private Range<Integer> mWidthRange;
+ private Range<Integer> mFrameRateRange;
+ private List<PerformancePoint> mPerformancePoints;
+
+ private int mWidthAlignment;
+ private int mHeightAlignment;
+
+ // Used by JNI to construct Java VideoCapsNativeImpl
+ /** package private */ VideoCapsNativeImpl(Range<Integer> bitrateRange,
+ Range<Integer> widthRange, Range<Integer> heightRange,
+ Range<Integer> frameRateRange, List<PerformancePoint> performancePoints,
+ int widthAlignment, int heightAlignment) {
+ mBitrateRange = new Range<Integer>(bitrateRange.getLower(),
+ bitrateRange.getUpper());
+ mWidthRange = new Range<Integer>(widthRange.getLower(), widthRange.getUpper());
+ mHeightRange = new Range<Integer>(heightRange.getLower(), heightRange.getUpper());
+ mFrameRateRange = new Range<Integer>(frameRateRange.getLower(),
+ frameRateRange.getUpper());
+ mPerformancePoints = new ArrayList<PerformancePoint>();
+ for (PerformancePoint pp : performancePoints) {
+ mPerformancePoints.add(new PerformancePoint(pp));
}
+ mWidthAlignment = widthAlignment;
+ mHeightAlignment = heightAlignment;
+ }
- final int blockSize = 8;
- int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
- maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
- maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- blockSize, blockSize,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else {
- Log.w(TAG, "Unsupported mime " + mime);
- // using minimal bitrate here. should be overriden by
- // info from media_codecs.xml
- maxBps = 64000;
- errors |= ERROR_UNSUPPORTED;
- }
- mBitrateRange = Range.create(1, maxBps);
- mParent.mError |= errors;
+ /* no public constructor */
+ private VideoCapsNativeImpl() { }
+
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
+
+ public Range<Integer> getSupportedWidths() {
+ return mWidthRange;
+ }
+
+ public Range<Integer> getSupportedHeights() {
+ return mHeightRange;
+ }
+
+ public int getWidthAlignment() {
+ return mWidthAlignment;
+ }
+
+ public int getHeightAlignment() {
+ return mHeightAlignment;
+ }
+
+ /** @hide */
+ public int getSmallerDimensionUpperLimit() {
+ return native_getSmallerDimensionUpperLimit();
+ }
+
+ public Range<Integer> getSupportedFrameRates() {
+ return mFrameRateRange;
+ }
+
+ @Nullable
+ public List<PerformancePoint> getSupportedPerformancePoints() {
+ return mPerformancePoints;
+ }
+
+ public Range<Integer> getSupportedWidthsFor(int height) {
+ return native_getSupportedWidthsFor(height);
+ }
+
+ public Range<Integer> getSupportedHeightsFor(int width) {
+ return native_getSupportedHeightsFor(width);
+ }
+
+ public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+ return native_getSupportedFrameRatesFor(width, height);
+ }
+
+ /** @throws IllegalArgumentException if the video size is not supported. */
+ @Nullable
+ public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+ return native_getAchievableFrameRatesFor(width, height);
+ }
+
+ public boolean areSizeAndRateSupported(int width, int height, double frameRate) {
+ return native_areSizeAndRateSupported(width, height, frameRate);
+ }
+
+ public boolean isSizeSupported(int width, int height) {
+ return native_isSizeSupported(width, height);
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ throw new UnsupportedOperationException(
+ "Java Implementation should not call native implemenatation");
+ }
+
+ private native Range<Integer> native_getSupportedWidthsFor(int height);
+ private native Range<Integer> native_getSupportedHeightsFor(int width);
+ private native Range<Double> native_getSupportedFrameRatesFor(int width, int height);
+ private native Range<Double> native_getAchievableFrameRatesFor(int width, int height);
+ private native boolean native_areSizeAndRateSupported(
+ int width, int height, double frameRate);
+ private native boolean native_isSizeSupported(int width, int height);
+ private native int native_getSmallerDimensionUpperLimit();
+
+ private static native void native_init();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
}
- }
- /**
- * A class that supports querying the encoding capabilities of a codec.
- */
- public static final class EncoderCapabilities {
+ private VideoCapsIntf mImpl;
+
+ /** @hide */
+ public static VideoCapabilities create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent);
+ VideoCapabilities caps = new VideoCapabilities(impl);
+ return caps;
+ }
+
+ /* package private */ VideoCapabilities(VideoCapsIntf impl) {
+ mImpl = impl;
+ }
+
+ /* no public constructor */
+ private VideoCapabilities() { }
+
/**
- * Returns the supported range of quality values.
+ * Returns the range of supported bitrates in bits/second.
+ */
+ public Range<Integer> getBitrateRange() {
+ return mImpl.getBitrateRange();
+ }
+
+ /**
+ * Returns the range of supported video widths.
+ * <p class=note>
+ * 32-bit processes will not support resolutions larger than 4096x4096 due to
+ * the limited address space.
+ */
+ public Range<Integer> getSupportedWidths() {
+ return mImpl.getSupportedWidths();
+ }
+
+ /**
+ * Returns the range of supported video heights.
+ * <p class=note>
+ * 32-bit processes will not support resolutions larger than 4096x4096 due to
+ * the limited address space.
+ */
+ public Range<Integer> getSupportedHeights() {
+ return mImpl.getSupportedHeights();
+ }
+
+ /**
+ * Returns the alignment requirement for video width (in pixels).
*
- * Quality is implementation-specific. As a general rule, a higher quality
- * setting results in a better image quality and a lower compression ratio.
+ * This is a power-of-2 value that video width must be a
+ * multiple of.
*/
- public Range<Integer> getQualityRange() {
- return mQualityRange;
+ public int getWidthAlignment() {
+ return mImpl.getWidthAlignment();
}
/**
- * Returns the supported range of encoder complexity values.
+ * Returns the alignment requirement for video height (in pixels).
+ *
+ * This is a power-of-2 value that video height must be a
+ * multiple of.
+ */
+ public int getHeightAlignment() {
+ return mImpl.getWidthAlignment();
+ }
+
+ /**
+ * Return the upper limit on the smaller dimension of width or height.
+ * <p></p>
+ * Some codecs have a limit on the smaller dimension, whether it be
+ * the width or the height. E.g. a codec may only be able to handle
+ * up to 1920x1080 both in landscape and portrait mode (1080x1920).
+ * In this case the maximum width and height are both 1920, but the
+ * smaller dimension limit will be 1080. For other codecs, this is
+ * {@code Math.min(getSupportedWidths().getUpper(),
+ * getSupportedHeights().getUpper())}.
+ *
+ * @hide
+ */
+ public int getSmallerDimensionUpperLimit() {
+ return mImpl.getSmallerDimensionUpperLimit();
+ }
+
+ /**
+ * Returns the range of supported frame rates.
* <p>
- * Some codecs may support multiple complexity levels, where higher
- * complexity values use more encoder tools (e.g. perform more
- * intensive calculations) to improve the quality or the compression
- * ratio. Use a lower value to save power and/or time.
+ * This is not a performance indicator. Rather, it expresses the
+ * limits specified in the coding standard, based on the complexities
+ * of encoding material for later playback at a certain frame rate,
+ * or the decoding of such material in non-realtime.
*/
- public Range<Integer> getComplexityRange() {
- return mComplexityRange;
+ public Range<Integer> getSupportedFrameRates() {
+ return mImpl.getSupportedFrameRates();
+ }
+
+ /**
+ * Returns the range of supported video widths for a video height.
+ * @param height the height of the video
+ */
+ public Range<Integer> getSupportedWidthsFor(int height) {
+ return mImpl.getSupportedWidthsFor(height);
+ }
+
+ /**
+ * Returns the range of supported video heights for a video width
+ * @param width the width of the video
+ */
+ public Range<Integer> getSupportedHeightsFor(int width) {
+ return mImpl.getSupportedHeightsFor(width);
+ }
+
+ /**
+ * Returns the range of supported video frame rates for a video size.
+ * <p>
+ * This is not a performance indicator. Rather, it expresses the limits specified in
+ * the coding standard, based on the complexities of encoding material of a given
+ * size for later playback at a certain frame rate, or the decoding of such material
+ * in non-realtime.
+
+ * @param width the width of the video
+ * @param height the height of the video
+ */
+ public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+ return mImpl.getSupportedFrameRatesFor(width, height);
}
+ /**
+ * Returns the range of achievable video frame rates for a video size.
+ * May return {@code null}, if the codec did not publish any measurement
+ * data.
+ * <p>
+ * This is a performance estimate provided by the device manufacturer based on statistical
+ * sampling of full-speed decoding and encoding measurements in various configurations
+ * of common video sizes supported by the codec. As such it should only be used to
+ * compare individual codecs on the device. The value is not suitable for comparing
+ * different devices or even different android releases for the same device.
+ * <p>
+ * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
+ * corresponds to the fastest frame rates achieved in the tested configurations. As
+ * such, it should not be used to gauge guaranteed or even average codec performance
+ * on the device.
+ * <p>
+ * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
+ * corresponds closer to sustained performance <em>in tested configurations</em>.
+ * One can expect to achieve sustained performance higher than the lower limit more than
+ * 50% of the time, and higher than half of the lower limit at least 90% of the time
+ * <em>in tested configurations</em>.
+ * Conversely, one can expect performance lower than twice the upper limit at least
+ * 90% of the time.
+ * <p class=note>
+ * Tested configurations use a single active codec. For use cases where multiple
+ * codecs are active, applications can expect lower and in most cases significantly lower
+ * performance.
+ * <p class=note>
+ * The returned range value is interpolated from the nearest frame size(s) tested.
+ * Codec performance is severely impacted by other activity on the device as well
+ * as environmental factors (such as battery level, temperature or power source), and can
+ * vary significantly even in a steady environment.
+ * <p class=note>
+ * Use this method in cases where only codec performance matters, e.g. to evaluate if
+ * a codec has any chance of meeting a performance target. Codecs are listed
+ * in {@link MediaCodecList} in the preferred order as defined by the device
+ * manufacturer. As such, applications should use the first suitable codec in the
+ * list to achieve the best balance between power use and performance.
+ *
+ * @param width the width of the video
+ * @param height the height of the video
+ *
+ * @throws IllegalArgumentException if the video size is not supported.
+ */
+ @Nullable
+ public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+ return mImpl.getAchievableFrameRatesFor(width, height);
+ }
+
+ /**
+ * Returns the supported performance points. May return {@code null} if the codec did not
+ * publish any performance point information (e.g. the vendor codecs have not been updated
+ * to the latest android release). May return an empty list if the codec published that
+ * if does not guarantee any performance points.
+ * <p>
+ * This is a performance guarantee provided by the device manufacturer for hardware codecs
+ * based on hardware capabilities of the device.
+ * <p>
+ * The returned list is sorted first by decreasing number of pixels, then by decreasing
+ * width, and finally by decreasing frame rate.
+ * Performance points assume a single active codec. For use cases where multiple
+ * codecs are active, should use that highest pixel count, and add the frame rates of
+ * each individual codec.
+ * <p class=note>
+ * 32-bit processes will not support resolutions larger than 4096x4096 due to
+ * the limited address space, but performance points will be presented as is.
+ * In other words, even though a component publishes a performance point for
+ * a resolution higher than 4096x4096, it does not mean that the resolution is supported
+ * for 32-bit processes.
+ */
+ @Nullable
+ public List<PerformancePoint> getSupportedPerformancePoints() {
+ return mImpl.getSupportedPerformancePoints();
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) and {@code frameRate} combination is supported.
+ */
+ public boolean areSizeAndRateSupported(int width, int height, double frameRate) {
+ return mImpl.areSizeAndRateSupported(width, height, frameRate);
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) is supported.
+ */
+ public boolean isSizeSupported(int width, int height) {
+ return mImpl.isSizeSupported(width, height);
+ }
+
+ /**
+ * @hide
+ * @throws java.lang.ClassCastException
+ * @throws java.lang.UnsupportedOperationException
+ */
+ public boolean supportsFormat(MediaFormat format) {
+ return mImpl.supportsFormat(format);
+ }
+ }
+
+ /**
+ * A class that supports querying the encoding capabilities of a codec.
+ */
+ public static final class EncoderCapabilities {
+ private static final String TAG = "EncoderCapabilities";
+
/** Constant quality mode */
public static final int BITRATE_MODE_CQ = 0;
/** Variable bitrate mode */
@@ -3874,188 +4563,314 @@ public final class MediaCodecInfo {
/** Constant bitrate mode with frame drops */
public static final int BITRATE_MODE_CBR_FD = 3;
- private static final Feature[] bitrates = new Feature[] {
- new Feature("VBR", BITRATE_MODE_VBR, true),
- new Feature("CBR", BITRATE_MODE_CBR, false),
- new Feature("CQ", BITRATE_MODE_CQ, false),
- new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
- };
-
- private static int parseBitrateMode(String mode) {
- for (Feature feat: bitrates) {
- if (feat.mName.equalsIgnoreCase(mode)) {
- return feat.mValue;
- }
- }
- return 0;
- }
+ /* package private */ interface EncoderCapsIntf {
+ public Range<Integer> getQualityRange();
- /**
- * Query whether a bitrate mode is supported.
- */
- public boolean isBitrateModeSupported(int mode) {
- for (Feature feat: bitrates) {
- if (mode == feat.mValue) {
- return (mBitControl & (1 << mode)) != 0;
- }
- }
- return false;
- }
+ public Range<Integer> getComplexityRange();
- private Range<Integer> mQualityRange;
- private Range<Integer> mComplexityRange;
- private CodecCapabilities mParent;
+ public boolean isBitrateModeSupported(int mode);
- /* no public constructor */
- private EncoderCapabilities() { }
+ public void getDefaultFormat(MediaFormat format);
- /** @hide */
- public static EncoderCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- EncoderCapabilities caps = new EncoderCapabilities();
- caps.init(info, parent);
- return caps;
+ public boolean supportsFormat(MediaFormat format);
}
- private void init(MediaFormat info, CodecCapabilities parent) {
- // no support for complexity or quality yet
- mParent = parent;
- mComplexityRange = Range.create(0, 0);
- mQualityRange = Range.create(0, 0);
- mBitControl = (1 << BITRATE_MODE_VBR);
+ /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf {
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
- applyLevelLimits();
- parseFromInfo(info);
- }
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
- private void applyLevelLimits() {
- String mime = mParent.getMimeType();
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
- mComplexityRange = Range.create(0, 8);
- mBitControl = (1 << BITRATE_MODE_CQ);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
- mBitControl = (1 << BITRATE_MODE_CBR);
+ public Range<Integer> getQualityRange() {
+ return mQualityRange;
}
- }
- private int mBitControl;
- private Integer mDefaultComplexity;
- private Integer mDefaultQuality;
- private String mQualityScale;
+ public Range<Integer> getComplexityRange() {
+ return mComplexityRange;
+ }
- private void parseFromInfo(MediaFormat info) {
- Map<String, Object> map = info.getMap();
+ private static final Feature[] bitrates = new Feature[] {
+ new Feature("VBR", BITRATE_MODE_VBR, true),
+ new Feature("CBR", BITRATE_MODE_CBR, false),
+ new Feature("CQ", BITRATE_MODE_CQ, false),
+ new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
+ };
- if (info.containsKey("complexity-range")) {
- mComplexityRange = Utils
- .parseIntRange(info.getString("complexity-range"), mComplexityRange);
- // TODO should we limit this to level limits?
+ private static int parseBitrateMode(String mode) {
+ for (Feature feat: bitrates) {
+ if (feat.mName.equalsIgnoreCase(mode)) {
+ return feat.mValue;
+ }
+ }
+ return 0;
}
- if (info.containsKey("quality-range")) {
- mQualityRange = Utils
- .parseIntRange(info.getString("quality-range"), mQualityRange);
+
+ public boolean isBitrateModeSupported(int mode) {
+ for (Feature feat: bitrates) {
+ if (mode == feat.mValue) {
+ return (mBitControl & (1 << mode)) != 0;
+ }
+ }
+ return false;
}
- if (info.containsKey("feature-bitrate-modes")) {
- mBitControl = 0;
- for (String mode: info.getString("feature-bitrate-modes").split(",")) {
- mBitControl |= (1 << parseBitrateMode(mode));
+
+ /* no public constructor */
+ private EncoderCapsLegacyImpl() { }
+
+ /** @hide */
+ public static EncoderCapsLegacyImpl create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+ Log.d(TAG, "Legacy implementation is called while native flag is on.");
}
+
+ EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl();
+ caps.init(info, parent);
+ return caps;
}
- try {
- mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
- } catch (NumberFormatException e) { }
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ // no support for complexity or quality yet
+ mParent = parent;
+ mComplexityRange = Range.create(0, 0);
+ mQualityRange = Range.create(0, 0);
+ mBitControl = (1 << BITRATE_MODE_VBR);
- try {
- mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
- } catch (NumberFormatException e) { }
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
- mQualityScale = (String)map.get("quality-scale");
- }
+ private void applyLevelLimits() {
+ String mime = mParent.getMimeType();
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ mComplexityRange = Range.create(0, 8);
+ mBitControl = (1 << BITRATE_MODE_CQ);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+ mBitControl = (1 << BITRATE_MODE_CBR);
+ }
+ }
+
+ private int mBitControl;
+ private Integer mDefaultComplexity;
+ private Integer mDefaultQuality;
+ private String mQualityScale;
- private boolean supports(
- Integer complexity, Integer quality, Integer profile) {
- boolean ok = true;
- if (ok && complexity != null) {
- ok = mComplexityRange.contains(complexity);
+ private void parseFromInfo(MediaFormat info) {
+ Map<String, Object> map = info.getMap();
+
+ if (info.containsKey("complexity-range")) {
+ mComplexityRange = Utils
+ .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+ // TODO should we limit this to level limits?
+ }
+ if (info.containsKey("quality-range")) {
+ mQualityRange = Utils
+ .parseIntRange(info.getString("quality-range"), mQualityRange);
+ }
+ if (info.containsKey("feature-bitrate-modes")) {
+ mBitControl = 0;
+ for (String mode: info.getString("feature-bitrate-modes").split(",")) {
+ mBitControl |= (1 << parseBitrateMode(mode));
+ }
+ }
+
+ try {
+ mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+ } catch (NumberFormatException e) { }
+
+ try {
+ mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+ } catch (NumberFormatException e) { }
+
+ mQualityScale = (String)map.get("quality-scale");
}
- if (ok && quality != null) {
- ok = mQualityRange.contains(quality);
+
+ private boolean supports(
+ Integer complexity, Integer quality, Integer profile) {
+ boolean ok = true;
+ if (ok && complexity != null) {
+ ok = mComplexityRange.contains(complexity);
+ }
+ if (ok && quality != null) {
+ ok = mQualityRange.contains(quality);
+ }
+ if (ok && profile != null) {
+ for (CodecProfileLevel pl: mParent.getProfileLevels()) {
+ if (pl.profile == profile) {
+ profile = null;
+ break;
+ }
+ }
+ ok = profile == null;
+ }
+ return ok;
}
- if (ok && profile != null) {
- for (CodecProfileLevel pl: mParent.profileLevels) {
- if (pl.profile == profile) {
- profile = null;
+
+ /** @hide */
+ public void getDefaultFormat(MediaFormat format) {
+ // don't list trivial quality/complexity as default for now
+ if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
+ && mDefaultQuality != null) {
+ format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+ }
+ if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
+ && mDefaultComplexity != null) {
+ format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
+ }
+ // bitrates are listed in order of preference
+ for (Feature feat: bitrates) {
+ if ((mBitControl & (1 << feat.mValue)) != 0) {
+ format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
break;
}
}
- ok = profile == null;
}
- return ok;
- }
- /** @hide */
- public void getDefaultFormat(MediaFormat format) {
- // don't list trivial quality/complexity as default for now
- if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
- && mDefaultQuality != null) {
- format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
- }
- if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
- && mDefaultComplexity != null) {
- format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
- }
- // bitrates are listed in order of preference
- for (Feature feat: bitrates) {
- if ((mBitControl & (1 << feat.mValue)) != 0) {
- format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
- break;
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = mParent.getMimeType();
+
+ Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+ if (mode != null && !isBitrateModeSupported(mode)) {
+ return false;
+ }
+
+ Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
+ if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
+ Integer flacComplexity =
+ (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
+ if (complexity == null) {
+ complexity = flacComplexity;
+ } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
+ throw new IllegalArgumentException(
+ "conflicting values for complexity and " +
+ "flac-compression-level");
+ }
}
+
+ // other audio parameters
+ Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+ if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
+ Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
+ if (profile == null) {
+ profile = aacProfile;
+ } else if (aacProfile != null && !aacProfile.equals(profile)) {
+ throw new IllegalArgumentException(
+ "conflicting values for profile and aac-profile");
+ }
+ }
+
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+ return supports(complexity, quality, profile);
}
}
- /** @hide */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = mParent.getMimeType();
+ /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf {
+ private long mNativeContext; // accessed by native methods
- Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
- if (mode != null && !isBitrateModeSupported(mode)) {
- return false;
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
+
+ /* no public constructor */
+ private EncoderCapsNativeImpl() { }
+
+ // Constructor called from native
+ /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange,
+ Range<Integer> complexityRange) {
+ mQualityRange = qualityRange;
+ mComplexityRange = complexityRange;
}
- Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
- if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
- Integer flacComplexity =
- (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
- if (complexity == null) {
- complexity = flacComplexity;
- } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
- throw new IllegalArgumentException(
- "conflicting values for complexity and " +
- "flac-compression-level");
- }
+ public Range<Integer> getQualityRange() {
+ return mQualityRange;
}
- // other audio parameters
- Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
- if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
- Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
- if (profile == null) {
- profile = aacProfile;
- } else if (aacProfile != null && !aacProfile.equals(profile)) {
- throw new IllegalArgumentException(
- "conflicting values for profile and aac-profile");
- }
+ public Range<Integer> getComplexityRange() {
+ return mComplexityRange;
+ }
+
+ public boolean isBitrateModeSupported(int mode) {
+ return native_isBitrateModeSupported(mode);
}
- Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+ // This API is for internal Java implementation only. Should not be called.
+ public void getDefaultFormat(MediaFormat format) {
+ throw new UnsupportedOperationException(
+ "Java Implementation should not call native implemenatation");
+ }
+
+ // This API is for internal Java implementation only. Should not be called.
+ public boolean supportsFormat(MediaFormat format) {
+ throw new UnsupportedOperationException(
+ "Java Implementation should not call native implemenatation");
+ }
- return supports(complexity, quality, profile);
+ private native boolean native_isBitrateModeSupported(int mode);
+ private static native void native_init();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+ }
+
+ private EncoderCapsIntf mImpl;
+
+ /** @hide */
+ public static EncoderCapabilities create(
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+ EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent);
+ EncoderCapabilities caps = new EncoderCapabilities(impl);
+ return caps;
+ }
+
+ /* package private */ EncoderCapabilities(EncoderCapsIntf impl) {
+ mImpl = impl;
+ }
+
+ /**
+ * Returns the supported range of quality values.
+ *
+ * Quality is implementation-specific. As a general rule, a higher quality
+ * setting results in a better image quality and a lower compression ratio.
+ */
+ public Range<Integer> getQualityRange() {
+ return mImpl.getQualityRange();
+ }
+
+ /**
+ * Returns the supported range of encoder complexity values.
+ * <p>
+ * Some codecs may support multiple complexity levels, where higher
+ * complexity values use more encoder tools (e.g. perform more
+ * intensive calculations) to improve the quality or the compression
+ * ratio. Use a lower value to save power and/or time.
+ */
+ public Range<Integer> getComplexityRange() {
+ return mImpl.getComplexityRange();
+ }
+
+ /**
+ * Query whether a bitrate mode is supported.
+ */
+ public boolean isBitrateModeSupported(int mode) {
+ return mImpl.isBitrateModeSupported(mode);
+ }
+
+ /** @hide */
+ public void getDefaultFormat(MediaFormat format) {
+ mImpl.getDefaultFormat(format);
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ return mImpl.supportsFormat(format);
}
};
@@ -4960,4 +5775,19 @@ public final class MediaCodecInfo {
mName, mCanonicalName, mFlags,
caps.toArray(new CodecCapabilities[caps.size()]));
}
+
+ /* package private */ class GenericHelper {
+ private static Range<Integer> constructIntegerRange(int lower, int upper) {
+ return Range.create(Integer.valueOf(lower), Integer.valueOf(upper));
+ }
+
+ private static Range<Double> constructDoubleRange(double lower, double upper) {
+ return Range.create(Double.valueOf(lower), Double.valueOf(upper));
+ }
+
+ private static List<VideoCapabilities.PerformancePoint>
+ constructPerformancePointList(VideoCapabilities.PerformancePoint[] array) {
+ return Arrays.asList(array);
+ }
+ }
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 09f40e005b4c..f42017dc835a 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -226,6 +226,10 @@ public abstract class MediaRoute2ProviderService extends Service {
@GuardedBy("mSessionLock")
private final ArrayMap<String, MediaStreams> mOngoingMediaStreams = new ArrayMap<>();
+ @GuardedBy("mSessionLock")
+ private final ArrayMap<String, RoutingSessionInfo> mPendingSystemSessionReleases =
+ new ArrayMap<>();
+
public MediaRoute2ProviderService() {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -358,7 +362,9 @@ public abstract class MediaRoute2ProviderService extends Service {
* @return a {@link MediaStreams} instance that holds the media streams to route as part of the
* newly created routing session. May be null if system media capture failed, in which case
* you can ignore the return value, as you will receive a call to {@link #onReleaseSession}
- * where you can clean up this session
+ * where you can clean up this session. {@link AudioRecord#startRecording()} must be called
+ * immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order
+ * to start streaming audio to the receiver.
* @hide
*/
// TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@@ -417,7 +423,7 @@ public abstract class MediaRoute2ProviderService extends Service {
}
AudioFormat audioFormat = formats.mAudioFormat;
- var mediaStreamsBuilder = new MediaStreams.Builder();
+ var mediaStreamsBuilder = new MediaStreams.Builder(sessionInfo);
if (audioFormat != null) {
populateAudioStream(audioFormat, uid, mediaStreamsBuilder);
}
@@ -458,7 +464,6 @@ public abstract class MediaRoute2ProviderService extends Service {
if (uid != Process.INVALID_UID) {
audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
}
-
AudioMix mix =
new AudioMix.Builder(audioMixingRuleBuilder.build())
.setFormat(audioFormat)
@@ -471,7 +476,11 @@ public abstract class MediaRoute2ProviderService extends Service {
Log.e(TAG, "Couldn't fetch the audio manager.");
return;
}
- audioManager.registerAudioPolicy(audioPolicy);
+ int audioPolicyResult = audioManager.registerAudioPolicy(audioPolicy);
+ if (audioPolicyResult != AudioManager.SUCCESS) {
+ Log.e(TAG, "Failed to register the audio policy.");
+ return;
+ }
var audioRecord = audioPolicy.createAudioRecordSink(mix);
if (audioRecord == null) {
Log.e(TAG, "Audio record creation failed.");
@@ -521,8 +530,14 @@ public abstract class MediaRoute2ProviderService extends Service {
RoutingSessionInfo sessionInfo;
synchronized (mSessionLock) {
sessionInfo = mSessionInfos.remove(sessionId);
- maybeReleaseMediaStreams(sessionId);
-
+ if (Flags.enableMirroringInMediaRouter2()) {
+ if (sessionInfo == null) {
+ sessionInfo = maybeReleaseMediaStreams(sessionId);
+ }
+ if (sessionInfo == null) {
+ sessionInfo = mPendingSystemSessionReleases.remove(sessionId);
+ }
+ }
if (sessionInfo == null) {
Log.w(TAG, "notifySessionReleased: Ignoring unknown session info.");
return;
@@ -539,18 +554,26 @@ public abstract class MediaRoute2ProviderService extends Service {
}
}
- /** Releases any system media routing resources associated with the given {@code sessionId}. */
- private void maybeReleaseMediaStreams(String sessionId) {
+ /**
+ * Releases any system media routing resources associated with the given {@code sessionId}.
+ *
+ * @return The {@link RoutingSessionInfo} that corresponds to the released media streams, or
+ * null if no streams were released.
+ */
+ @Nullable
+ private RoutingSessionInfo maybeReleaseMediaStreams(String sessionId) {
if (!Flags.enableMirroringInMediaRouter2()) {
- return;
+ return null;
}
synchronized (mSessionLock) {
var streams = mOngoingMediaStreams.remove(sessionId);
if (streams != null) {
releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord);
// TODO: b/380431086: Release the video stream once implemented.
+ return streams.mSessionInfo;
}
}
+ return null;
}
// We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it.
@@ -1019,12 +1042,16 @@ public abstract class MediaRoute2ProviderService extends Service {
if (!checkCallerIsSystem()) {
return;
}
- if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
- return;
+ synchronized (mSessionLock) {
+ // We proactively release the system media routing session resources when the
+ // system requests it, to ensure it happens immediately.
+ RoutingSessionInfo releasedSession = maybeReleaseMediaStreams(sessionId);
+ if (releasedSession != null) {
+ mPendingSystemSessionReleases.put(sessionId, releasedSession);
+ } else if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
+ return;
+ }
}
- // We proactively release the system media routing once the system requests it, to
- // ensure it happens immediately.
- maybeReleaseMediaStreams(sessionId);
addRequestId(requestId);
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
@@ -1047,9 +1074,19 @@ public abstract class MediaRoute2ProviderService extends Service {
@Nullable private final AudioPolicy mAudioPolicy;
@Nullable private final AudioRecord mAudioRecord;
+ /**
+ * Holds the last {@link RoutingSessionInfo} associated with these streams.
+ *
+ * @hide
+ */
+ @GuardedBy("MediaRoute2ProviderService.this.mSessionLock")
+ @NonNull
+ private RoutingSessionInfo mSessionInfo;
+
// TODO: b/380431086: Add the video equivalent.
private MediaStreams(Builder builder) {
+ this.mSessionInfo = builder.mSessionInfo;
this.mAudioPolicy = builder.mAudioPolicy;
this.mAudioRecord = builder.mAudioRecord;
}
@@ -1070,9 +1107,19 @@ public abstract class MediaRoute2ProviderService extends Service {
*/
public static final class Builder {
+ @NonNull private RoutingSessionInfo mSessionInfo;
@Nullable private AudioPolicy mAudioPolicy;
@Nullable private AudioRecord mAudioRecord;
+ /**
+ * Constructor.
+ *
+ * @param sessionInfo The {@link RoutingSessionInfo} associated with these streams.
+ */
+ Builder(@NonNull RoutingSessionInfo sessionInfo) {
+ mSessionInfo = requireNonNull(sessionInfo);
+ }
+
/** Populates system media audio-related structures. */
public Builder setAudioStream(
@NonNull AudioPolicy audioPolicy, @NonNull AudioRecord audioRecord) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f09dc7218d7d..af545d5a4bc4 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,6 +25,7 @@ cc_library_shared {
min_sdk_version: "",
srcs: [
+ "android_media_CodecCapabilities.cpp",
"android_media_ImageWriter.cpp",
"android_media_ImageReader.cpp",
"android_media_JetPlayer.cpp",
@@ -64,6 +65,7 @@ cc_library_shared {
"libbinder",
"libmedia",
"libmedia_codeclist",
+ "libmedia_codeclist_capabilities",
"libmedia_jni_utils",
"libmedia_omx",
"libmediametrics",
diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp
new file mode 100644
index 000000000000..df0c826d8d87
--- /dev/null
+++ b/media/jni/android_media_CodecCapabilities.cpp
@@ -0,0 +1,1015 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodec-JNI"
+
+#include "android_media_CodecCapabilities.h"
+#include "android_media_Streams.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+
+#include <media/AudioCapabilities.h>
+#include <media/CodecCapabilities.h>
+#include <media/EncoderCapabilities.h>
+#include <media/VideoCapabilities.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Log.h>
+
+namespace android {
+
+struct fields_t {
+ jfieldID audioCapsContext;
+ jfieldID videoCapsContext;
+ jfieldID encoderCapsContext;
+ jfieldID codecCapsContext;
+};
+static fields_t fields;
+
+// JCodecCapabilities
+
+JCodecCapabilities::JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps)
+ : mCodecCaps(codecCaps) {}
+
+std::shared_ptr<CodecCapabilities> JCodecCapabilities::getCodecCaps() const {
+ return mCodecCaps;
+}
+
+int32_t JCodecCapabilities::getMaxSupportedInstances() const {
+ return mCodecCaps->getMaxSupportedInstances();
+}
+
+std::string JCodecCapabilities::getMediaType() const {
+ return mCodecCaps->getMediaType();
+}
+
+bool JCodecCapabilities::isFeatureRequired(const std::string& name) const {
+ return mCodecCaps->isFeatureRequired(name);
+}
+
+bool JCodecCapabilities::isFeatureSupported(const std::string& name) const {
+ return mCodecCaps->isFeatureSupported(name);
+}
+
+bool JCodecCapabilities::isFormatSupported(const sp<AMessage> &format) const {
+ return mCodecCaps->isFormatSupported(format);
+}
+
+bool JCodecCapabilities::isRegular() const {
+ return mCodecCaps->isRegular();
+}
+
+// Setter
+
+static sp<JCodecCapabilities> setCodecCapabilities(JNIEnv *env, jobject thiz,
+ const sp<JCodecCapabilities>& jCodecCaps) {
+ sp<JCodecCapabilities> old
+ = (JCodecCapabilities*)env->GetLongField(thiz, fields.codecCapsContext);
+ if (jCodecCaps != NULL) {
+ jCodecCaps->incStrong(thiz);
+ }
+ if (old != NULL) {
+ old->decStrong(thiz);
+ }
+ env->SetLongField(thiz, fields.codecCapsContext, (jlong)jCodecCaps.get());
+ return old;
+}
+
+// Getters
+
+static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) {
+ AudioCapabilities* const p = (AudioCapabilities*)env->GetLongField(
+ thiz, fields.audioCapsContext);
+ return p;
+}
+
+static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) {
+ VideoCapabilities* const p = (VideoCapabilities*)env->GetLongField(
+ thiz, fields.videoCapsContext);
+ return p;
+}
+
+static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) {
+ EncoderCapabilities* const p = (EncoderCapabilities*)env->GetLongField(
+ thiz, fields.encoderCapsContext);
+ return p;
+}
+
+static sp<JCodecCapabilities> getCodecCapabilities(JNIEnv *env, jobject thiz) {
+ JCodecCapabilities* const p = (JCodecCapabilities*)env->GetLongField(
+ thiz, fields.codecCapsContext);
+ return sp<JCodecCapabilities>(p);
+}
+
+// Utils
+
+static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) {
+ jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+ jmethodID constructIntegerRangeID = env->GetStaticMethodID(helperClazz, "constructIntegerRange",
+ "(II)Landroid/util/Range;");
+ jobject jRange = env->CallStaticObjectMethod(helperClazz, constructIntegerRangeID,
+ range.lower(), range.upper());
+
+ return jRange;
+}
+
+static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range) {
+ jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+ jmethodID constructDoubleRangeID = env->GetStaticMethodID(helperClazz, "constructDoubleRange",
+ "(DD)Landroid/util/Range;");
+ jobject jRange = env->CallStaticObjectMethod(helperClazz, constructDoubleRangeID,
+ range.lower(), range.upper());
+ return jRange;
+}
+
+static jobjectArray convertToJavaIntRangeArray(JNIEnv *env,
+ const std::vector<Range<int32_t>>& ranges) {
+ jclass rangeClazz = env->FindClass("android/util/Range");
+ CHECK(rangeClazz != NULL);
+ jobjectArray jRanges = env->NewObjectArray(ranges.size(), rangeClazz, NULL);
+ for (int i = 0; i < ranges.size(); i++) {
+ Range<int32_t> range = ranges.at(i);
+ jobject jRange = convertToJavaIntRange(env, range);
+ env->SetObjectArrayElement(jRanges, i, jRange);
+ env->DeleteLocalRef(jRange);
+ jRange = NULL;
+ }
+ return jRanges;
+}
+
+// Converters between Java objects and native instances
+
+// The Java AudioCapabilities object keep bitrateRange, sampleRates, sampleRateRanges
+// and inputChannelRanges in it to prevent reconstruction when called the getters functions.
+static jobject convertToJavaAudioCapabilities(
+ JNIEnv *env, std::shared_ptr<AudioCapabilities> audioCaps) {
+ if (audioCaps == nullptr) {
+ return NULL;
+ }
+
+ // construct Java bitrateRange
+ const Range<int32_t>& bitrateRange = audioCaps->getBitrateRange();
+ jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
+
+ // construct Java sampleRates array
+ const std::vector<int32_t>& sampleRates = audioCaps->getSupportedSampleRates();
+ jintArray jSampleRates = env->NewIntArray(sampleRates.size());
+ for (size_t i = 0; i < sampleRates.size(); ++i) {
+ jint val = sampleRates.at(i);
+ env->SetIntArrayRegion(jSampleRates, i, 1, &val);
+ }
+
+ // construct Java sampleRateRanges
+ const std::vector<Range<int32_t>>& sampleRateRanges = audioCaps->getSupportedSampleRateRanges();
+ jobjectArray jSampleRateRanges = convertToJavaIntRangeArray(env, sampleRateRanges);
+
+ // construct Java inputChannelRanges
+ const std::vector<Range<int32_t>>& inputChannelRanges = audioCaps->getInputChannelCountRanges();
+ jobjectArray jInputChannelRanges = convertToJavaIntRangeArray(env, inputChannelRanges);
+
+ // construct Java AudioCapsNativeImpl
+ jclass audioCapsImplClazz
+ = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
+ CHECK(audioCapsImplClazz != NULL);
+ jmethodID audioCapsImplConstructID = env->GetMethodID(audioCapsImplClazz, "<init>",
+ "(Landroid/util/Range;"
+ "[I"
+ "[Landroid/util/Range;"
+ "[Landroid/util/Range;)V");
+ jobject jAudioCapsImpl = env->NewObject(audioCapsImplClazz, audioCapsImplConstructID,
+ jBitrateRange, jSampleRates, jSampleRateRanges, jInputChannelRanges);
+ // The native AudioCapabilities won't be destructed until process ends.
+ env->SetLongField(jAudioCapsImpl, fields.audioCapsContext, (jlong)audioCaps.get());
+
+ // construct Java AudioCapabilities
+ jclass audioCapsClazz
+ = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities");
+ CHECK(audioCapsClazz != NULL);
+ jmethodID audioCapsConstructID = env->GetMethodID(audioCapsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$AudioCapabilities$AudioCapsIntf;)V");
+ jobject jAudioCaps = env->NewObject(audioCapsClazz, audioCapsConstructID, jAudioCapsImpl);
+
+ env->DeleteLocalRef(jBitrateRange);
+ jBitrateRange = NULL;
+
+ env->DeleteLocalRef(jSampleRates);
+ jSampleRates = NULL;
+
+ env->DeleteLocalRef(jSampleRateRanges);
+ jSampleRateRanges = NULL;
+
+ env->DeleteLocalRef(jInputChannelRanges);
+ jInputChannelRanges = NULL;
+
+ env->DeleteLocalRef(jAudioCapsImpl);
+ jAudioCapsImpl = NULL;
+
+ return jAudioCaps;
+}
+
+// convert native PerformancePoints to Java objects
+static jobject convertToJavaPerformancePoints(JNIEnv *env,
+ const std::vector<VideoCapabilities::PerformancePoint>& performancePoints) {
+ jclass performancePointClazz = env->FindClass(
+ "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
+ CHECK(performancePointClazz != NULL);
+ jmethodID performancePointConstructID = env->GetMethodID(performancePointClazz, "<init>",
+ "(IIIJII)V");
+
+ jobjectArray jPerformancePoints = env->NewObjectArray(performancePoints.size(),
+ performancePointClazz, NULL);
+ int i = 0;
+ for (auto it = performancePoints.begin(); it != performancePoints.end(); ++it, ++i) {
+ jobject jPerformancePoint = env->NewObject(performancePointClazz,
+ performancePointConstructID, it->getWidth(),
+ it->getHeight(), it->getMaxFrameRate(),
+ it->getMaxMacroBlockRate(), it->getBlockSize().getWidth(),
+ it->getBlockSize().getHeight());
+
+ env->SetObjectArrayElement(jPerformancePoints, i, jPerformancePoint);
+
+ env->DeleteLocalRef(jPerformancePoint);
+ }
+
+ jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+ CHECK(helperClazz != NULL);
+ jmethodID asListID = env->GetStaticMethodID(helperClazz, "constructPerformancePointList",
+ "([Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Ljava/util/List;");
+ CHECK(asListID != NULL);
+ jobject jList = env->CallStaticObjectMethod(helperClazz, asListID, jPerformancePoints);
+
+ return jList;
+}
+
+static VideoCapabilities::PerformancePoint convertToNativePerformancePoint(
+ JNIEnv *env, jobject pp) {
+ if (pp == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
+ }
+
+ jclass clazz = env->FindClass(
+ "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
+ CHECK(clazz != NULL);
+ CHECK(env->IsInstanceOf(pp, clazz));
+
+ jmethodID getWidthID = env->GetMethodID(clazz, "getWidth", "()I");
+ CHECK(getWidthID != NULL);
+ jint width = env->CallIntMethod(pp, getWidthID);
+
+ jmethodID getHeightID = env->GetMethodID(clazz, "getHeight", "()I");
+ CHECK(getHeightID != NULL);
+ jint height = env->CallIntMethod(pp, getHeightID);
+
+ jmethodID getMaxFrameRateID = env->GetMethodID(clazz, "getMaxFrameRate", "()I");
+ CHECK(getMaxFrameRateID != NULL);
+ jint maxFrameRate = env->CallIntMethod(pp, getMaxFrameRateID);
+
+ jmethodID getMaxMacroBlockRateID = env->GetMethodID(clazz, "getMaxMacroBlockRate", "()J");
+ CHECK(getMaxMacroBlockRateID != NULL);
+ jlong maxMacroBlockRate = env->CallLongMethod(pp, getMaxMacroBlockRateID);
+
+ jmethodID getBlockWidthID = env->GetMethodID(clazz, "getBlockWidth", "()I");
+ CHECK(getBlockWidthID != NULL);
+ jint blockWidth = env->CallIntMethod(pp, getBlockWidthID);
+
+ jmethodID getBlockHeightID = env->GetMethodID(clazz, "getBlockHeight", "()I");
+ CHECK(getBlockHeightID != NULL);
+ jint blockHeight = env->CallIntMethod(pp, getBlockHeightID);
+
+ return VideoCapabilities::PerformancePoint(VideoSize(blockWidth, blockHeight),
+ width, height, maxFrameRate, maxMacroBlockRate);
+}
+
+static jobject convertToJavaVideoCapabilities(JNIEnv *env,
+ std::shared_ptr<VideoCapabilities> videoCaps) {
+ if (videoCaps == nullptr) {
+ return NULL;
+ }
+
+ // get Java bitrateRange
+ const Range<int32_t>& bitrateRange = videoCaps->getBitrateRange();
+ jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
+
+ // get Java widthRange
+ const Range<int32_t>& widthRange = videoCaps->getSupportedWidths();
+ jobject jWidthRange = convertToJavaIntRange(env, widthRange);
+
+ // get Java heightRange
+ const Range<int32_t>& heightRange = videoCaps->getSupportedHeights();
+ jobject jHeightRange = convertToJavaIntRange(env, heightRange);
+
+ // get Java frameRateRange
+ const Range<int32_t>& frameRateRange = videoCaps->getSupportedFrameRates();
+ jobject jFrameRateRange = convertToJavaIntRange(env, frameRateRange);
+
+ // get Java performancePoints
+ const std::vector<VideoCapabilities::PerformancePoint>& performancePoints
+ = videoCaps->getSupportedPerformancePoints();
+ jobject jPerformancePoints = convertToJavaPerformancePoints(env, performancePoints);
+
+ // get width alignment
+ int32_t widthAlignment = videoCaps->getWidthAlignment();
+
+ // get height alignment
+ int32_t heightAlignment = videoCaps->getHeightAlignment();
+
+ // get Java VideoCapsNativeImpl
+ jclass videoCapsImplClazz = env->FindClass(
+ "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
+ CHECK(videoCapsImplClazz != NULL);
+ jmethodID videoCapsImplConstructID = env->GetMethodID(videoCapsImplClazz, "<init>",
+ "(Landroid/util/Range;"
+ "Landroid/util/Range;"
+ "Landroid/util/Range;"
+ "Landroid/util/Range;"
+ "Ljava/util/List;II)V");
+ jobject jVideoCapsImpl = env->NewObject(videoCapsImplClazz, videoCapsImplConstructID,
+ jBitrateRange, jWidthRange, jHeightRange, jFrameRateRange, jPerformancePoints,
+ widthAlignment, heightAlignment);
+ // The native VideoCapabilities won't be destructed until process ends.
+ env->SetLongField(jVideoCapsImpl, fields.videoCapsContext, (jlong)videoCaps.get());
+
+ // get Java VideoCapabilities
+ jclass videoCapsClazz
+ = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities");
+ CHECK(videoCapsClazz != NULL);
+ jmethodID videoCapsConstructID = env->GetMethodID(videoCapsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$VideoCapabilities$VideoCapsIntf;)V");
+ jobject jVideoCaps = env->NewObject(videoCapsClazz, videoCapsConstructID, jVideoCapsImpl);
+
+ env->DeleteLocalRef(jBitrateRange);
+ jBitrateRange = NULL;
+
+ env->DeleteLocalRef(jWidthRange);
+ jWidthRange = NULL;
+
+ env->DeleteLocalRef(jHeightRange);
+ jHeightRange = NULL;
+
+ env->DeleteLocalRef(jFrameRateRange);
+ jFrameRateRange = NULL;
+
+ env->DeleteLocalRef(jPerformancePoints);
+ jPerformancePoints = NULL;
+
+ env->DeleteLocalRef(jVideoCapsImpl);
+ jVideoCapsImpl = NULL;
+
+ return jVideoCaps;
+}
+
+static jobject convertToJavaEncoderCapabilities(JNIEnv *env,
+ std::shared_ptr<EncoderCapabilities> encoderCaps) {
+ if (encoderCaps == nullptr) {
+ return NULL;
+ }
+
+ // get quality range
+ const Range<int>& qualityRange = encoderCaps->getQualityRange();
+ jobject jQualityRange = convertToJavaIntRange(env, qualityRange);
+
+ // get complexity range
+ const Range<int>& complexityRange = encoderCaps->getComplexityRange();
+ jobject jComplexityRange = convertToJavaIntRange(env, complexityRange);
+
+ // construct java EncoderCapsNativeImpl
+ jclass encoderCapsImplClazz = env->FindClass(
+ "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
+ CHECK(encoderCapsImplClazz != NULL);
+ jmethodID encoderCapsImplConstructID = env->GetMethodID(encoderCapsImplClazz, "<init>",
+ "(Landroid/util/Range;Landroid/util/Range;)V");
+ jobject jEncoderCapsImpl = env->NewObject(encoderCapsImplClazz, encoderCapsImplConstructID,
+ jQualityRange, jComplexityRange);
+ // The native EncoderCapabilities won't be destructed until process ends.
+ env->SetLongField(jEncoderCapsImpl, fields.encoderCapsContext, (jlong)encoderCaps.get());
+
+ // construct java EncoderCapabilities object
+ jclass encoderCapsClazz
+ = env->FindClass("android/media/MediaCodecInfo$EncoderCapabilities");
+ CHECK(encoderCapsClazz != NULL);
+ jmethodID encoderCapsConstructID = env->GetMethodID(encoderCapsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsIntf;)V");
+ jobject jEncoderCaps = env->NewObject(encoderCapsClazz, encoderCapsConstructID,
+ jEncoderCapsImpl);
+
+ env->DeleteLocalRef(jQualityRange);
+ jQualityRange = NULL;
+
+ env->DeleteLocalRef(jComplexityRange);
+ jComplexityRange = NULL;
+
+ env->DeleteLocalRef(jEncoderCapsImpl);
+ jEncoderCapsImpl = NULL;
+
+ return jEncoderCaps;
+}
+
+// Java CodecCapsNativeImpl keeps the defaultFormat, profileLevels, colorFormats, audioCapabilities,
+// videoCapabilities and encoderCapabilities in it to prevent reconsturction when called by getter.
+static jobject convertToJavaCodecCapsNativeImpl(
+ JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
+ // Construct defaultFormat
+ sp<AMessage> defaultFormat = codecCaps->getDefaultFormat();
+
+ jobject formatMap = NULL;
+ if (ConvertMessageToMap(env, defaultFormat, &formatMap)) {
+ return NULL;
+ }
+
+ ScopedLocalRef<jclass> mediaFormatClass{env, env->FindClass("android/media/MediaFormat")};
+ ScopedLocalRef<jobject> jDefaultFormat{env, env->NewObject(
+ mediaFormatClass.get(),
+ env->GetMethodID(mediaFormatClass.get(), "<init>", "(Ljava/util/Map;)V"),
+ formatMap)};
+
+ env->DeleteLocalRef(formatMap);
+ formatMap = NULL;
+
+ // Construct Java ProfileLevelArray
+ std::vector<ProfileLevel> profileLevels = codecCaps->getProfileLevels();
+
+ jclass profileLevelClazz =
+ env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+ CHECK(profileLevelClazz != NULL);
+
+ jobjectArray profileLevelArray =
+ env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+
+ jfieldID profileField =
+ env->GetFieldID(profileLevelClazz, "profile", "I");
+ jfieldID levelField =
+ env->GetFieldID(profileLevelClazz, "level", "I");
+
+ for (size_t i = 0; i < profileLevels.size(); ++i) {
+ const ProfileLevel &src = profileLevels.at(i);
+
+ jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+
+ env->SetIntField(profileLevelObj, profileField, src.mProfile);
+ env->SetIntField(profileLevelObj, levelField, src.mLevel);
+
+ env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+
+ env->DeleteLocalRef(profileLevelObj);
+ profileLevelObj = NULL;
+ }
+
+ // Construct ColorFormatArray
+ std::vector<uint32_t> colorFormats = codecCaps->getColorFormats();
+
+ jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+ env->SetIntArrayRegion(colorFormatsArray, 0, colorFormats.size(),
+ reinterpret_cast<jint*>(colorFormats.data()));
+
+ // Construct and set AudioCapabilities
+ std::shared_ptr<AudioCapabilities> audioCaps = codecCaps->getAudioCapabilities();
+ jobject jAudioCaps = convertToJavaAudioCapabilities(env, audioCaps);
+
+ // Set VideoCapabilities
+ std::shared_ptr<VideoCapabilities> videoCaps = codecCaps->getVideoCapabilities();
+ jobject jVideoCaps = convertToJavaVideoCapabilities(env, videoCaps);
+
+ // Set EncoderCapabilities
+ std::shared_ptr<EncoderCapabilities> encoderCaps = codecCaps->getEncoderCapabilities();
+ jobject jEncoderCaps = convertToJavaEncoderCapabilities(env, encoderCaps);
+
+ // Construct CodecCapsNativeImpl
+ jclass codecCapsImplClazz =
+ env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
+ CHECK(codecCapsImplClazz != NULL);
+ jmethodID codecCapsImplConstructID = env->GetMethodID(codecCapsImplClazz, "<init>",
+ "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[I"
+ "Landroid/media/MediaFormat;"
+ "Landroid/media/MediaCodecInfo$AudioCapabilities;"
+ "Landroid/media/MediaCodecInfo$VideoCapabilities;"
+ "Landroid/media/MediaCodecInfo$EncoderCapabilities;)V");
+ jobject javaCodecCapsImpl = env->NewObject(codecCapsImplClazz, codecCapsImplConstructID,
+ profileLevelArray, colorFormatsArray, jDefaultFormat.get(),
+ jAudioCaps, jVideoCaps, jEncoderCaps);
+
+ // Construct JCodecCapabilities and hold the codecCaps in it
+ sp<JCodecCapabilities> jCodecCaps = sp<JCodecCapabilities>::make(codecCaps);
+ setCodecCapabilities(env, javaCodecCapsImpl, jCodecCaps);
+
+ env->DeleteLocalRef(profileLevelArray);
+ profileLevelArray = NULL;
+
+ env->DeleteLocalRef(colorFormatsArray);
+ colorFormatsArray = NULL;
+
+ env->DeleteLocalRef(jAudioCaps);
+ jAudioCaps = NULL;
+
+ env->DeleteLocalRef(jVideoCaps);
+ jVideoCaps = NULL;
+
+ env->DeleteLocalRef(jEncoderCaps);
+ jEncoderCaps = NULL;
+
+ return javaCodecCapsImpl;
+}
+
+jobject convertToJavaCodecCapabiliites(
+ JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
+ jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
+
+ // Construct CodecCapabilities
+ jclass codecCapsClazz = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
+ CHECK(codecCapsClazz != NULL);
+
+ jmethodID codecCapsConstructID = env->GetMethodID(codecCapsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+ jobject javaCodecCaps = env->NewObject(codecCapsClazz, codecCapsConstructID, javaCodecCapsImpl);
+
+ return javaCodecCaps;
+}
+
+} // namespace android
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// AudioCapabilities
+
+static void android_media_AudioCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+ jclass audioCapsImplClazz
+ = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
+ if (audioCapsImplClazz == NULL) {
+ return;
+ }
+
+ fields.audioCapsContext = env->GetFieldID(audioCapsImplClazz, "mNativeContext", "J");
+ if (fields.audioCapsContext == NULL) {
+ return;
+ }
+
+ env->DeleteLocalRef(audioCapsImplClazz);
+}
+
+static jint android_media_AudioCapabilities_getMaxInputChannelCount(JNIEnv *env, jobject thiz) {
+ AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+ if (audioCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ int32_t maxInputChannelCount = audioCaps->getMaxInputChannelCount();
+ return maxInputChannelCount;
+}
+
+static jint android_media_AudioCapabilities_getMinInputChannelCount(JNIEnv *env, jobject thiz) {
+ AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+ if (audioCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ int32_t minInputChannelCount = audioCaps->getMinInputChannelCount();
+ return minInputChannelCount;
+}
+
+static jboolean android_media_AudioCapabilities_isSampleRateSupported(JNIEnv *env, jobject thiz,
+ int sampleRate) {
+ AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+ if (audioCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ bool res = audioCaps->isSampleRateSupported(sampleRate);
+ return res;
+}
+
+// PerformancePoint
+
+static jboolean android_media_VideoCapabilities_PerformancePoint_covers(JNIEnv *env, jobject thiz,
+ jobject other) {
+ VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
+ VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
+
+ bool res = pp0.covers(pp1);
+ return res;
+}
+
+static jboolean android_media_VideoCapabilities_PerformancePoint_equals(JNIEnv *env, jobject thiz,
+ jobject other) {
+ VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
+ VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
+
+ bool res = pp0.equals(pp1);
+ return res;
+}
+
+// VideoCapabilities
+
+static void android_media_VideoCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+ jclass clazz
+ = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
+ if (clazz == NULL) {
+ return;
+ }
+
+ fields.videoCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
+ if (fields.videoCapsContext == NULL) {
+ return;
+ }
+
+ env->DeleteLocalRef(clazz);
+}
+
+static jboolean android_media_VideoCapabilities_areSizeAndRateSupported(JNIEnv *env, jobject thiz,
+ int32_t width, int32_t height, double frameRate) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ bool res = videoCaps->areSizeAndRateSupported(width, height, frameRate);
+ return res;
+}
+
+static jboolean android_media_VideoCapabilities_isSizeSupported(JNIEnv *env, jobject thiz,
+ int32_t width, int32_t height) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ bool res = videoCaps->isSizeSupported(width, height);
+ return res;
+}
+
+static jobject android_media_VideoCapabilities_getAchievableFrameRatesFor(JNIEnv *env, jobject thiz,
+ int32_t width, int32_t height) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ std::optional<Range<double>> frameRates = videoCaps->getAchievableFrameRatesFor(width, height);
+ if (!frameRates) {
+ return NULL;
+ }
+ jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
+ return jFrameRates;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedFrameRatesFor(JNIEnv *env, jobject thiz,
+ int32_t width, int32_t height) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ std::optional<Range<double>> frameRates = videoCaps->getSupportedFrameRatesFor(width, height);
+ if (!frameRates) {
+ return NULL;
+ }
+ jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
+ return jFrameRates;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedWidthsFor(JNIEnv *env, jobject thiz,
+ int32_t height) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ std::optional<Range<int32_t>> supportedWidths = videoCaps->getSupportedWidthsFor(height);
+ if (!supportedWidths) {
+ return NULL;
+ }
+ jobject jSupportedWidths = convertToJavaIntRange(env, supportedWidths.value());
+
+ return jSupportedWidths;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedHeightsFor(JNIEnv *env, jobject thiz,
+ int32_t width) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ std::optional<Range<int32_t>> supportedHeights = videoCaps->getSupportedHeightsFor(width);
+ if (!supportedHeights) {
+ return NULL;
+ }
+ jobject jSupportedHeights = convertToJavaIntRange(env, supportedHeights.value());
+
+ return jSupportedHeights;
+}
+
+static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv *env,
+ jobject thiz) {
+ VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+ if (videoCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ int smallerDimensionUpperLimit = videoCaps->getSmallerDimensionUpperLimit();
+ return smallerDimensionUpperLimit;
+}
+
+// EncoderCapabilities
+
+static void android_media_EncoderCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+ jclass clazz = env->FindClass(
+ "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
+ if (clazz == NULL) {
+ return;
+ }
+
+ fields.encoderCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
+ if (fields.encoderCapsContext == NULL) {
+ return;
+ }
+
+ env->DeleteLocalRef(clazz);
+}
+
+static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv *env, jobject thiz,
+ int mode) {
+ EncoderCapabilities* const encoderCaps = getEncoderCapabilities(env, thiz);
+ if (encoderCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ bool res = encoderCaps->isBitrateModeSupported(mode);
+ return res;
+}
+
+// CodecCapabilities
+
+static void android_media_CodecCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+ jclass codecCapsClazz
+ = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
+ if (codecCapsClazz == NULL) {
+ return;
+ }
+
+ fields.codecCapsContext = env->GetFieldID(codecCapsClazz, "mNativeContext", "J");
+ if (fields.codecCapsContext == NULL) {
+ return;
+ }
+
+ env->DeleteLocalRef(codecCapsClazz);
+}
+
+static jobject android_media_CodecCapabilities_createFromProfileLevel(JNIEnv *env,
+ jobject /* thiz */, jstring mediaType, jint profile, jint level) {
+ if (mediaType == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
+
+ const char *mediaTypeStr = env->GetStringUTFChars(mediaType, nullptr);
+ if (mediaTypeStr == nullptr) {
+ return NULL;
+ }
+
+ std::shared_ptr<CodecCapabilities> codecCaps = CodecCapabilities::CreateFromProfileLevel(
+ mediaTypeStr, profile, level);
+
+ jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
+
+ env->ReleaseStringUTFChars(mediaType, mediaTypeStr);
+
+ return javaCodecCapsImpl;
+}
+
+static jobject android_media_CodecCapabilities_native_dup(JNIEnv *env, jobject thiz) {
+ sp<JCodecCapabilities> jCodecCaps = getCodecCapabilities(env, thiz);
+
+ // As the CodecCaps objects are ready ony, it is ok to use the default copy constructor.
+ // The duplicate CodecCaps will share the same subobjects with the existing one.
+ // The lifetime of subobjects are managed by the shared pointer and sp.
+ std::shared_ptr<CodecCapabilities> duplicate
+ = std::make_shared<CodecCapabilities>(*(jCodecCaps->getCodecCaps()));
+
+ jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, duplicate);
+
+ return javaCodecCapsImpl;
+}
+
+static void android_media_CodecCapabilities_native_finalize(JNIEnv *env, jobject thiz) {
+ ALOGV("native_finalize");
+ setCodecCapabilities(env, thiz, NULL);
+}
+
+static jint android_media_CodecCapabilities_getMaxSupportedInstances(JNIEnv *env, jobject thiz) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ int maxSupportedInstances = codecCaps->getMaxSupportedInstances();
+ return maxSupportedInstances;
+}
+
+static jstring android_media_CodecCapabilities_getMimeType(JNIEnv *env, jobject thiz) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ std::string mediaType = codecCaps->getMediaType();
+ return env->NewStringUTF(mediaType.c_str());
+}
+
+static jboolean android_media_CodecCapabilities_isFeatureRequired(
+ JNIEnv *env, jobject thiz, jstring name) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ if (name == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return -ENOENT;
+ }
+
+ const char *nameStr = env->GetStringUTFChars(name, NULL);
+ if (nameStr == NULL) {
+ // Out of memory exception already pending.
+ return -ENOENT;
+ }
+
+ bool isFeatureRequired = codecCaps->isFeatureRequired(nameStr);
+
+ env->ReleaseStringUTFChars(name, nameStr);
+
+ return isFeatureRequired;
+}
+
+static jboolean android_media_CodecCapabilities_isFeatureSupported(
+ JNIEnv *env, jobject thiz, jstring name) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ if (name == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return -ENOENT;
+ }
+
+ const char *nameStr = env->GetStringUTFChars(name, NULL);
+ if (nameStr == NULL) {
+ // Out of memory exception already pending.
+ return -ENOENT;
+ }
+
+ bool isFeatureSupported = codecCaps->isFeatureSupported(nameStr);
+
+ env->ReleaseStringUTFChars(name, nameStr);
+
+ return isFeatureSupported;
+}
+
+static jboolean android_media_CodecCapabilities_isFormatSupported(JNIEnv *env, jobject thiz,
+ jobjectArray keys, jobjectArray values) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ sp<AMessage> format;
+ status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return -ENOENT;;
+ }
+
+ return codecCaps->isFormatSupported(format);
+}
+
+static jboolean android_media_CodecCapabilities_isRegular(JNIEnv *env, jobject thiz) {
+ sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+ if (codecCaps == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ bool res = codecCaps->isRegular();
+ return res;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gAudioCapsMethods[] = {
+ {"native_init", "()V", (void *)android_media_AudioCapabilities_native_init},
+ {"native_getMaxInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMaxInputChannelCount},
+ {"native_getMinInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMinInputChannelCount},
+ {"native_isSampleRateSupported", "(I)Z", (void *)android_media_AudioCapabilities_isSampleRateSupported}
+};
+
+static const JNINativeMethod gPerformancePointMethods[] = {
+ {"native_covers", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_covers},
+ {"native_equals", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_equals},
+};
+
+static const JNINativeMethod gVideoCapsMethods[] = {
+ {"native_init", "()V", (void *)android_media_VideoCapabilities_native_init},
+ {"native_areSizeAndRateSupported", "(IID)Z", (void *)android_media_VideoCapabilities_areSizeAndRateSupported},
+ {"native_isSizeSupported", "(II)Z", (void *)android_media_VideoCapabilities_isSizeSupported},
+ {"native_getAchievableFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getAchievableFrameRatesFor},
+ {"native_getSupportedFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedFrameRatesFor},
+ {"native_getSupportedWidthsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedWidthsFor},
+ {"native_getSupportedHeightsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedHeightsFor},
+ {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit}
+};
+
+static const JNINativeMethod gEncoderCapsMethods[] = {
+ {"native_init", "()V", (void *)android_media_EncoderCapabilities_native_init},
+ {"native_isBitrateModeSupported", "(I)Z", (void *)android_media_EncoderCapabilities_isBitrateModeSupported}
+};
+
+static const JNINativeMethod gCodecCapsMethods[] = {
+ { "native_init", "()V", (void *)android_media_CodecCapabilities_native_init },
+ { "native_createFromProfileLevel", "(Ljava/lang/String;II)Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_createFromProfileLevel },
+ { "native_dup", "()Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_native_dup },
+ { "native_finalize", "()V", (void *)android_media_CodecCapabilities_native_finalize },
+ { "native_getMaxSupportedInstances", "()I", (void *)android_media_CodecCapabilities_getMaxSupportedInstances },
+ { "native_getMimeType", "()Ljava/lang/String;", (void *)android_media_CodecCapabilities_getMimeType },
+ { "native_isFeatureRequired", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureRequired },
+ { "native_isFeatureSupported", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureSupported },
+ { "native_isFormatSupported", "([Ljava/lang/String;[Ljava/lang/Object;)Z", (void *)android_media_CodecCapabilities_isFormatSupported },
+ { "native_isRegular", "()Z", (void *)android_media_CodecCapabilities_isRegular },
+};
+
+int register_android_media_CodecCapabilities(JNIEnv *env) {
+ int result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl",
+ gAudioCapsMethods, NELEM(gAudioCapsMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint",
+ gPerformancePointMethods, NELEM(gPerformancePointMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl",
+ gVideoCapsMethods, NELEM(gVideoCapsMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl",
+ gEncoderCapsMethods, NELEM(gEncoderCapsMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl",
+ gCodecCapsMethods, NELEM(gCodecCapsMethods));
+ return result;
+} \ No newline at end of file
diff --git a/media/jni/android_media_CodecCapabilities.h b/media/jni/android_media_CodecCapabilities.h
new file mode 100644
index 000000000000..5cca0b503740
--- /dev/null
+++ b/media/jni/android_media_CodecCapabilities.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_CODECCAPABILITIES_H_
+#define _ANDROID_MEDIA_CODECCAPABILITIES_H_
+
+#include "jni.h"
+
+#include <media/CodecCapabilities.h>
+
+namespace android {
+
+struct JCodecCapabilities : public RefBase {
+ JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps);
+
+ std::shared_ptr<CodecCapabilities> getCodecCaps() const;
+
+ int32_t getMaxSupportedInstances() const;
+ std::string getMediaType() const;
+ bool isFeatureRequired(const std::string& name) const;
+ bool isFeatureSupported(const std::string& name) const;
+ bool isFormatSupported(const sp<AMessage> &format) const;
+ bool isRegular() const;
+
+private:
+ std::shared_ptr<CodecCapabilities> mCodecCaps;
+};
+
+jobject convertToJavaCodecCapabiliites(
+ JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps);
+
+}
+
+#endif // _ANDROID_MEDIA_CODECCAPABILITIES_H_ \ No newline at end of file
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8419ce761a4a..1790670903a4 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,12 +16,14 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaCodec-JNI"
+#include <android_media_codec.h>
#include <utils/Log.h>
#include <type_traits>
#include "android_media_MediaCodec.h"
+#include "android_media_CodecCapabilities.h"
#include "android_media_MediaCodecLinearBlock.h"
#include "android_media_MediaCrypto.h"
#include "android_media_MediaDescrambler.h"
@@ -138,6 +140,8 @@ static struct {
static struct {
jclass capsClazz;
jmethodID capsCtorId;
+ jclass cpasImplClazz;
+ jmethodID capsImplCtorId;
jclass profileLevelClazz;
jfieldID profileField;
jfieldID levelField;
@@ -996,10 +1000,12 @@ static jobject getCodecCapabilitiesObject(
env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val);
}
- return env->NewObject(
- gCodecInfo.capsClazz, gCodecInfo.capsCtorId,
+ jobject javaCodecCapsImpl = env->NewObject(
+ gCodecInfo.cpasImplClazz, gCodecInfo.capsImplCtorId,
profileLevelArray.get(), colorFormatsArray.get(), isEncoder,
defaultFormatRef.get(), detailsRef.get());
+
+ return env->NewObject(gCodecInfo.capsClazz, gCodecInfo.capsCtorId, javaCodecCapsImpl);
}
status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const {
@@ -1027,11 +1033,18 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const
env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL));
for (size_t i = 0; i < mediaTypes.size(); i++) {
- const sp<MediaCodecInfo::Capabilities> caps =
- codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
-
- ScopedLocalRef<jobject> capsObj(env, getCodecCapabilitiesObject(
- env, mediaTypes[i].c_str(), isEncoder, caps));
+ jobject jCodecCaps = NULL;
+ if (android::media::codec::provider_->native_capabilites()) {
+ const std::shared_ptr<CodecCapabilities> codecCaps
+ = codecInfo->getCodecCapsFor(mediaTypes[i].c_str());
+ jCodecCaps = convertToJavaCodecCapabiliites(env, codecCaps);
+ } else {
+ const sp<MediaCodecInfo::Capabilities> caps =
+ codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
+ jCodecCaps = getCodecCapabilitiesObject(
+ env, mediaTypes[i].c_str(), isEncoder, caps);
+ }
+ ScopedLocalRef<jobject> capsObj(env, jCodecCaps);
env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get());
}
@@ -3877,10 +3890,20 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get());
method = env->GetMethodID(clazz.get(), "<init>",
+ "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+ CHECK(method != NULL);
+ gCodecInfo.capsCtorId = method;
+
+ clazz.reset(env->FindClass(
+ "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl"));
+ CHECK(clazz.get() != NULL);
+ gCodecInfo.cpasImplClazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ method = env->GetMethodID(clazz.get(), "<init>",
"([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
"Ljava/util/Map;Ljava/util/Map;)V");
CHECK(method != NULL);
- gCodecInfo.capsCtorId = method;
+ gCodecInfo.capsImplCtorId = method;
clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"));
CHECK(clazz.get() != NULL);
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index 07866ac34e4c..3522b35539ab 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -16,6 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaCodec-JNI"
+
+#include <android_media_codec.h>
+
#include <utils/Log.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -32,6 +35,7 @@
#include "android_runtime/AndroidRuntime.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include "android_media_CodecCapabilities.h"
#include "android_media_Streams.h"
using namespace android;
@@ -245,95 +249,113 @@ static jobject android_media_MediaCodecList_getCodecCapabilities(
return NULL;
}
- Vector<MediaCodecInfo::ProfileLevel> profileLevels;
- Vector<uint32_t> colorFormats;
+ jobject caps;
+ if (android::media::codec::provider_->native_capabilites()) {
+ std::shared_ptr<CodecCapabilities> codecCaps = info.info->getCodecCapsFor(typeStr);
+ caps = android::convertToJavaCodecCapabiliites(env, codecCaps);
+ } else {
+ Vector<MediaCodecInfo::ProfileLevel> profileLevels;
+ Vector<uint32_t> colorFormats;
+
+ sp<AMessage> defaultFormat = new AMessage();
+ defaultFormat->setString("mime", typeStr);
+
+ // TODO query default-format also from codec/codec list
+ const sp<MediaCodecInfo::Capabilities> &capabilities =
+ info.info->getCapabilitiesFor(typeStr);
+ env->ReleaseStringUTFChars(type, typeStr);
+ typeStr = NULL;
+ if (capabilities == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
- sp<AMessage> defaultFormat = new AMessage();
- defaultFormat->setString("mime", typeStr);
+ capabilities->getSupportedColorFormats(&colorFormats);
+ capabilities->getSupportedProfileLevels(&profileLevels);
+ sp<AMessage> details = capabilities->getDetails();
+ bool isEncoder = info.info->isEncoder();
- // TODO query default-format also from codec/codec list
- const sp<MediaCodecInfo::Capabilities> &capabilities =
- info.info->getCapabilitiesFor(typeStr);
- env->ReleaseStringUTFChars(type, typeStr);
- typeStr = NULL;
- if (capabilities == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- return NULL;
- }
+ jobject defaultFormatObj = NULL;
+ if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+ return NULL;
+ }
- capabilities->getSupportedColorFormats(&colorFormats);
- capabilities->getSupportedProfileLevels(&profileLevels);
- sp<AMessage> details = capabilities->getDetails();
- bool isEncoder = info.info->isEncoder();
+ jobject infoObj = NULL;
+ if (ConvertMessageToMap(env, details, &infoObj)) {
+ env->DeleteLocalRef(defaultFormatObj);
+ return NULL;
+ }
- jobject defaultFormatObj = NULL;
- if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
- return NULL;
- }
+ jclass capsImplClazz = env->FindClass(
+ "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl");
+ CHECK(capsImplClazz != NULL);
- jobject infoObj = NULL;
- if (ConvertMessageToMap(env, details, &infoObj)) {
- env->DeleteLocalRef(defaultFormatObj);
- return NULL;
- }
+ jclass profileLevelClazz =
+ env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+ CHECK(profileLevelClazz != NULL);
- jclass capsClazz =
- env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
- CHECK(capsClazz != NULL);
+ jobjectArray profileLevelArray =
+ env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
- jclass profileLevelClazz =
- env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
- CHECK(profileLevelClazz != NULL);
+ jfieldID profileField =
+ env->GetFieldID(profileLevelClazz, "profile", "I");
- jobjectArray profileLevelArray =
- env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+ jfieldID levelField =
+ env->GetFieldID(profileLevelClazz, "level", "I");
- jfieldID profileField =
- env->GetFieldID(profileLevelClazz, "profile", "I");
+ for (size_t i = 0; i < profileLevels.size(); ++i) {
+ const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
- jfieldID levelField =
- env->GetFieldID(profileLevelClazz, "level", "I");
+ jobject profileLevelObj = env->AllocObject(profileLevelClazz);
- for (size_t i = 0; i < profileLevels.size(); ++i) {
- const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
+ env->SetIntField(profileLevelObj, profileField, src.mProfile);
+ env->SetIntField(profileLevelObj, levelField, src.mLevel);
- jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+ env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
- env->SetIntField(profileLevelObj, profileField, src.mProfile);
- env->SetIntField(profileLevelObj, levelField, src.mLevel);
+ env->DeleteLocalRef(profileLevelObj);
+ profileLevelObj = NULL;
+ }
- env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+ jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
- env->DeleteLocalRef(profileLevelObj);
- profileLevelObj = NULL;
- }
+ for (size_t i = 0; i < colorFormats.size(); ++i) {
+ jint val = colorFormats.itemAt(i);
+ env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
+ }
- jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+ jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>",
+ "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
+ "Ljava/util/Map;Ljava/util/Map;)V");
- for (size_t i = 0; i < colorFormats.size(); ++i) {
- jint val = colorFormats.itemAt(i);
- env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
- }
+ jobject capsImpl = env->NewObject(capsImplClazz, capsImplConstructID,
+ profileLevelArray, colorFormatsArray, isEncoder,
+ defaultFormatObj, infoObj);
+
+ jclass capsClazz = env->FindClass(
+ "android/media/MediaCodecInfo$CodecCapabilities");
+ CHECK(capsClazz != NULL);
- jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
- "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
- "Ljava/util/Map;Ljava/util/Map;)V");
+ jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
- jobject caps = env->NewObject(capsClazz, capsConstructID,
- profileLevelArray, colorFormatsArray, isEncoder,
- defaultFormatObj, infoObj);
+ caps = env->NewObject(capsClazz, capsConstructID, capsImpl);
- env->DeleteLocalRef(profileLevelArray);
- profileLevelArray = NULL;
+ env->DeleteLocalRef(profileLevelArray);
+ profileLevelArray = NULL;
- env->DeleteLocalRef(colorFormatsArray);
- colorFormatsArray = NULL;
+ env->DeleteLocalRef(colorFormatsArray);
+ colorFormatsArray = NULL;
- env->DeleteLocalRef(defaultFormatObj);
- defaultFormatObj = NULL;
+ env->DeleteLocalRef(defaultFormatObj);
+ defaultFormatObj = NULL;
+
+ env->DeleteLocalRef(infoObj);
+ infoObj = NULL;
- env->DeleteLocalRef(infoObj);
- infoObj = NULL;
+ env->DeleteLocalRef(capsImpl);
+ capsImpl = NULL;
+ }
return caps;
}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index a94230014437..f159282ed2c2 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1479,6 +1479,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env)
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_JetPlayer(JNIEnv *env);
+extern int register_android_media_CodecCapabilities(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_Descrambler(JNIEnv *env);
@@ -1593,6 +1594,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
goto bail;
}
+ if (register_android_media_CodecCapabilities(env) < 0) {
+ ALOGE("ERROR: CodecCapabilities native registration failed");
+ goto bail;
+ }
+
if (register_android_media_Crypto(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;
diff --git a/nfc/tests/src/android/nfc/NfcManagerTest.java b/nfc/tests/src/android/nfc/NfcManagerTest.java
new file mode 100644
index 000000000000..06314cc03d37
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcManagerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class NfcManagerTest {
+
+ private MockitoSession mMockitoSession;
+ private NfcManager mNfcManager;
+ @Mock
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(NfcAdapter.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ when(NfcAdapter.getNfcAdapter(any())).thenReturn(mock(NfcAdapter.class));
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ mNfcManager = new NfcManager(mContext);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testGetDefaultAdapter() {
+ NfcAdapter nfcAdapter = mNfcManager.getDefaultAdapter();
+ assertThat(nfcAdapter).isNotNull();
+ }
+}
diff --git a/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
new file mode 100644
index 000000000000..6be95adbeba0
--- /dev/null
+++ b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class CardemulationTest {
+
+ private CardEmulation mCardEmulation;
+ @Mock
+ private Context mContext;
+ @Mock
+ private INfcCardEmulation mINfcCardEmulation;
+ @Mock
+ private NfcAdapter mNfcAdapter;
+ @Mock
+ private PackageManager mPackageManager;
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() {
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(NfcAdapter.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION))
+ .thenReturn(true);
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ assertThat(mNfcAdapter).isNotNull();
+ when(mNfcAdapter.getCardEmulationService()).thenReturn(mINfcCardEmulation);
+ when(mNfcAdapter.getContext()).thenReturn(mContext);
+ mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testIsDefaultServiceForCategory() throws RemoteException {
+ ComponentName componentName = mock(ComponentName.class);
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ when(mINfcCardEmulation.isDefaultServiceForCategory(1, componentName,
+ "payment")).thenReturn(true);
+ boolean result = mCardEmulation.isDefaultServiceForCategory(componentName,
+ "payment");
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).isDefaultServiceForCategory(1, componentName,
+ "payment");
+
+ }
+
+ @Test
+ public void testIsDefaultServiceForAid() throws RemoteException {
+ ComponentName componentName = mock(ComponentName.class);
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ when(mINfcCardEmulation.isDefaultServiceForAid(1, componentName,
+ "payment")).thenReturn(true);
+ boolean result = mCardEmulation.isDefaultServiceForAid(componentName,
+ "payment");
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).isDefaultServiceForAid(1, componentName,
+ "payment");
+ }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcATest.java b/nfc/tests/src/android/nfc/tech/NfcATest.java
new file mode 100644
index 000000000000..40076ebd0a0a
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcATest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcA.EXTRA_ATQA;
+import static android.nfc.tech.NfcA.EXTRA_SAK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcATest {
+ @Mock
+ private Tag mMockTag;
+ @Mock
+ private INfcTag mMockTagService;
+ @Mock
+ private Bundle mMockBundle;
+ private NfcA mNfcA;
+ private final byte[] mSampleArray = new byte[] {1, 2, 3};
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockBundle.getShort(EXTRA_SAK)).thenReturn((short) 1);
+ when(mMockBundle.getByteArray(EXTRA_ATQA)).thenReturn(mSampleArray);
+ when(mMockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle);
+
+ mNfcA = new NfcA(mMockTag);
+ }
+
+ @Test
+ public void testGetNfcAWithTech() {
+ Tag mockTag = mock(Tag.class);
+ when(mockTag.getTechExtras(TagTechnology.NFC_A)).thenReturn(mMockBundle);
+ when(mockTag.hasTech(TagTechnology.NFC_A)).thenReturn(true);
+
+ assertNotNull(NfcA.get(mockTag));
+ verify(mockTag).getTechExtras(TagTechnology.NFC_A);
+ verify(mockTag).hasTech(TagTechnology.NFC_A);
+ }
+
+ @Test
+ public void testGetNfcAWithoutTech() {
+ when(mMockTag.hasTech(TagTechnology.NFC_A)).thenReturn(false);
+ assertNull(NfcA.get(mMockTag));
+ }
+
+ @Test
+ public void testGetAtga() {
+ assertNotNull(mNfcA.getAtqa());
+ }
+
+ @Test
+ public void testGetSak() {
+ assertEquals((short) 1, mNfcA.getSak());
+ }
+
+ @Test
+ public void testTransceive() throws IOException, RemoteException {
+ TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+ when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_A);
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTag.getServiceHandle()).thenReturn(1);
+ when(mMockTagService.transceive(1, mSampleArray, true))
+ .thenReturn(mockTransceiveResult);
+ when(mockTransceiveResult.getResponseOrThrow()).thenReturn(mSampleArray);
+
+ mNfcA.transceive(mSampleArray);
+ verify(mMockTag).getTagService();
+ verify(mMockTag).getServiceHandle();
+ }
+
+ @Test
+ public void testGetMaxTransceiveLength() throws RemoteException {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_A)).thenReturn(1);
+
+ mNfcA.getMaxTransceiveLength();
+ verify(mMockTag).getTagService();
+ }
+
+ @Test
+ public void testSetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenReturn(
+ ErrorCodes.SUCCESS);
+
+ mNfcA.setTimeout(1000);
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutInvalidTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_A, -1)).thenReturn(
+ ErrorCodes.ERROR_TIMEOUT);
+
+ assertThrows(IllegalArgumentException.class, () -> mNfcA.setTimeout(-1));
+ } catch (Exception e) {
+ fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_A, 1000)).thenThrow(
+ new RemoteException());
+
+ mNfcA.setTimeout(1000); // Should not throw an exception but log it
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_A, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+ }
+
+ }
+
+ @Test
+ public void testGetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenReturn(2000);
+
+ assertEquals(2000, mNfcA.getTimeout());
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).getTimeout(TagTechnology.NFC_A);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_A)).thenThrow(new RemoteException());
+
+ assertEquals(0, mNfcA.getTimeout());
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+ }
+ }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcBTest.java b/nfc/tests/src/android/nfc/tech/NfcBTest.java
new file mode 100644
index 000000000000..98d6070e760c
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcBTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcB.EXTRA_APPDATA;
+import static android.nfc.tech.NfcB.EXTRA_PROTINFO;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcBTest {
+ private final byte[] mSampleAppDate = new byte[] {1, 2, 3};
+ private final byte[] mSampleProtInfo = new byte[] {3, 2, 1};
+ @Mock
+ private Tag mMockTag;
+ @Mock
+ private Bundle mMockBundle;
+ @Mock
+ private INfcTag mMockTagService;
+ private NfcB mNfcB;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockBundle.getByteArray(EXTRA_APPDATA)).thenReturn(mSampleAppDate);
+ when(mMockBundle.getByteArray(EXTRA_PROTINFO)).thenReturn(mSampleProtInfo);
+ when(mMockTag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+ mNfcB = new NfcB(mMockTag);
+ }
+
+ @Test
+ public void testGetApplicationData() {
+ assertNotNull(mNfcB.getApplicationData());
+ }
+
+ @Test
+ public void testGetProtocolInfo() {
+ assertNotNull(mNfcB.getProtocolInfo());
+ }
+
+ @Test
+ public void testGetNfcBInstance() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(true);
+ when(tag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+ assertNotNull(NfcB.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_B);
+ verify(tag).getTechExtras(TagTechnology.NFC_B);
+ }
+
+ @Test
+ public void testGetNfcBNullInstance() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(false);
+
+ assertNull(NfcB.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_B);
+ verify(tag, never()).getTechExtras(TagTechnology.NFC_B);
+ }
+
+
+ @Test
+ public void testTransceive() throws IOException, RemoteException {
+ byte[] sampleData = new byte[] {1, 2, 3, 4, 5};
+ TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+ when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_B);
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTag.getServiceHandle()).thenReturn(1);
+ when(mMockTagService.transceive(1, sampleData, true))
+ .thenReturn(mockTransceiveResult);
+ when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+ mNfcB.transceive(sampleData);
+ verify(mMockTag).getTagService();
+ verify(mMockTag).getServiceHandle();
+ }
+
+ @Test
+ public void testGetMaxTransceiveLength() throws RemoteException {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_B)).thenReturn(1);
+
+ mNfcB.getMaxTransceiveLength();
+ verify(mMockTag).getTagService();
+ }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcFTest.java b/nfc/tests/src/android/nfc/tech/NfcFTest.java
new file mode 100644
index 000000000000..31a6943566e0
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcFTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcF.EXTRA_PMM;
+import static android.nfc.tech.NfcF.EXTRA_SC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcFTest {
+ private final byte[] mSampleSystemCode = new byte[] {1, 2, 3};
+ private final byte[] mSampleManufacturer = new byte[] {3, 2, 1};
+ @Mock
+ private Tag mMockTag;
+ @Mock
+ private INfcTag mMockTagService;
+ @Mock
+ private Bundle mMockBundle;
+ private NfcF mNfcF;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockBundle.getByteArray(EXTRA_SC)).thenReturn(mSampleSystemCode);
+ when(mMockBundle.getByteArray(EXTRA_PMM)).thenReturn(mSampleManufacturer);
+ when(mMockTag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+
+ mNfcF = new NfcF(mMockTag);
+ }
+
+ @Test
+ public void testGetSystemCode() {
+ assertNotNull(mNfcF.getSystemCode());
+ }
+
+ @Test
+ public void testGetManufacturer() {
+ assertNotNull(mNfcF.getManufacturer());
+ }
+
+ @Test
+ public void testGetNfcFInstanceWithTech() {
+ Tag tag = mock(Tag.class);
+ when(tag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+ when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(true);
+
+ assertNotNull(NfcF.get(tag));
+ verify(tag).getTechExtras(TagTechnology.NFC_F);
+ verify(tag).hasTech(TagTechnology.NFC_F);
+ }
+
+ @Test
+ public void testGetNfcFInstanceWithoutTech() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(false);
+
+ assertNull(NfcF.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_F);
+ verify(tag, never()).getTechExtras(TagTechnology.NFC_F);
+ }
+
+ @Test
+ public void testTransceive() throws IOException, RemoteException {
+ byte[] sampleData = new byte[]{1, 2, 3, 4, 5};
+ TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+ when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_F);
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTag.getServiceHandle()).thenReturn(1);
+ when(mMockTagService.transceive(1, sampleData, true))
+ .thenReturn(mockTransceiveResult);
+ when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+ mNfcF.transceive(sampleData);
+ verify(mMockTag).getTagService();
+ verify(mMockTag).getServiceHandle();
+ }
+
+ @Test
+ public void testGetMaxTransceiveLength() throws RemoteException {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_F)).thenReturn(1);
+
+ mNfcF.getMaxTransceiveLength();
+ verify(mMockTag).getTagService();
+ }
+
+ @Test
+ public void testGetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenReturn(2000);
+
+ assertEquals(2000, mNfcF.getTimeout());
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).getTimeout(TagTechnology.NFC_F);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenThrow(new RemoteException());
+
+ assertEquals(0, mNfcF.getTimeout());
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenReturn(
+ ErrorCodes.SUCCESS);
+
+ mNfcF.setTimeout(1000);
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutInvalidTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, -1)).thenReturn(
+ ErrorCodes.ERROR_TIMEOUT);
+
+ assertThrows(IllegalArgumentException.class, () -> mNfcF.setTimeout(-1));
+ } catch (Exception e) {
+ fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenThrow(
+ new RemoteException());
+
+ mNfcF.setTimeout(1000);
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index 31e1eb36ad8d..70f5bb32a5d5 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -2074,15 +2074,19 @@ public class PackageWatchdog {
bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
}
+ FileOutputStream fileStream = null;
+ ObjectOutputStream objectStream = null;
try {
- FileOutputStream fileStream = new FileOutputStream(new File(filePath));
- ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
+ fileStream = new FileOutputStream(new File(filePath));
+ objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(bootMitigationCounts);
objectStream.flush();
- objectStream.close();
- fileStream.close();
} catch (Exception e) {
Slog.i(TAG, "Could not save observers metadata to file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
}
}
@@ -2233,23 +2237,32 @@ public class PackageWatchdog {
void readAllObserversBootMitigationCountIfNecessary(String filePath) {
File metadataFile = new File(filePath);
if (metadataFile.exists()) {
+ FileInputStream fileStream = null;
+ ObjectInputStream objectStream = null;
+ HashMap<String, Integer> bootMitigationCounts = null;
try {
- FileInputStream fileStream = new FileInputStream(metadataFile);
- ObjectInputStream objectStream = new ObjectInputStream(fileStream);
- HashMap<String, Integer> bootMitigationCounts =
+ fileStream = new FileInputStream(metadataFile);
+ objectStream = new ObjectInputStream(fileStream);
+ bootMitigationCounts =
(HashMap<String, Integer>) objectStream.readObject();
- objectStream.close();
- fileStream.close();
-
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- if (bootMitigationCounts.containsKey(observer.name)) {
- observer.setBootMitigationCount(
- bootMitigationCounts.get(observer.name));
- }
- }
} catch (Exception e) {
Slog.i(TAG, "Could not read observer metadata file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
+ }
+
+ if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
+ Slog.i(TAG, "No observer in metadata file");
+ return;
+ }
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ if (bootMitigationCounts.containsKey(observer.name)) {
+ observer.setBootMitigationCount(
+ bootMitigationCounts.get(observer.name));
+ }
}
}
}
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index ffae5176cebf..ac815f8aca85 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -2021,15 +2021,19 @@ public class PackageWatchdog {
bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
}
+ FileOutputStream fileStream = null;
+ ObjectOutputStream objectStream = null;
try {
- FileOutputStream fileStream = new FileOutputStream(new File(filePath));
- ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);
+ fileStream = new FileOutputStream(new File(filePath));
+ objectStream = new ObjectOutputStream(fileStream);
objectStream.writeObject(bootMitigationCounts);
objectStream.flush();
- objectStream.close();
- fileStream.close();
} catch (Exception e) {
Slog.i(TAG, "Could not save observers metadata to file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
}
}
@@ -2180,23 +2184,32 @@ public class PackageWatchdog {
void readAllObserversBootMitigationCountIfNecessary(String filePath) {
File metadataFile = new File(filePath);
if (metadataFile.exists()) {
+ FileInputStream fileStream = null;
+ ObjectInputStream objectStream = null;
+ HashMap<String, Integer> bootMitigationCounts = null;
try {
- FileInputStream fileStream = new FileInputStream(metadataFile);
- ObjectInputStream objectStream = new ObjectInputStream(fileStream);
- HashMap<String, Integer> bootMitigationCounts =
+ fileStream = new FileInputStream(metadataFile);
+ objectStream = new ObjectInputStream(fileStream);
+ bootMitigationCounts =
(HashMap<String, Integer>) objectStream.readObject();
- objectStream.close();
- fileStream.close();
-
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- if (bootMitigationCounts.containsKey(observer.name)) {
- observer.setBootMitigationCount(
- bootMitigationCounts.get(observer.name));
- }
- }
} catch (Exception e) {
Slog.i(TAG, "Could not read observer metadata file: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(objectStream);
+ IoUtils.closeQuietly(fileStream);
+ }
+
+ if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
+ Slog.i(TAG, "No observer in metadata file");
+ return;
+ }
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ if (bootMitigationCounts.containsKey(observer.name)) {
+ observer.setBootMitigationCount(
+ bootMitigationCounts.get(observer.name));
+ }
}
}
}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index e029f3a16066..4da73593bdea 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+ <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
<uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 635ae20cfa38..6c06fab16502 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -26,6 +26,7 @@ import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
@@ -274,8 +275,20 @@ public class InstallStart extends Activity {
}
private boolean canPackageQuery(int callingUid, Uri packageUri) {
- ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
- PackageManager.ComponentInfoFlags.of(0));
+ ProviderInfo info;
+ try {
+ if (Flags.uidBasedProviderLookup()) {
+ info = mPackageManager.resolveContentProviderForUid(packageUri.getAuthority(),
+ PackageManager.ComponentInfoFlags.of(0), callingUid);
+ } else {
+ info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
+ PackageManager.ComponentInfoFlags.of(0));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Caller cannot access " + packageUri, e);
+ return false;
+ }
+
if (info == null) {
return false;
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
index 53507fe46d1f..6360af573d5c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt
@@ -82,5 +82,11 @@ class SettingsGlobalStore private constructor(contentResolver: ContentResolver)
instance = it
}
}
+
+ /** Returns the required permissions to read [Global] settings. */
+ fun getReadPermissions() = arrayOf<String>()
+
+ /** Returns the required permissions to write [Global] settings. */
+ fun getWritePermissions() = arrayOf<String>()
}
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
index ca7fd7bb5f1e..c117b926d1eb 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.datastore
+import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
@@ -82,5 +83,11 @@ class SettingsSecureStore private constructor(contentResolver: ContentResolver)
instance = it
}
}
+
+ /** Returns the required permissions to read [Secure] settings. */
+ fun getReadPermissions() = arrayOf<String>()
+
+ /** Returns the required permissions to write [Secure] settings. */
+ fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS)
}
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
index 20a74d3b4a81..f5a2f940bc03 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.datastore
+import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
@@ -82,5 +83,11 @@ class SettingsSystemStore private constructor(contentResolver: ContentResolver)
instance = it
}
}
+
+ /** Returns the required permissions to read [System] settings. */
+ fun getReadPermissions() = arrayOf<String>()
+
+ /** Returns the required permissions to write [System] settings. */
+ fun getWritePermissions() = arrayOf(Manifest.permission.WRITE_SETTINGS)
}
}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index f611793b619b..2aa619aa67f9 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -81,6 +81,10 @@ message PreferenceProto {
optional PreferenceValueDescriptorProto value_descriptor = 15;
// Indicate how sensitive of the preference.
optional int32 sensitivity_level = 16;
+ // The required permissions to read preference value.
+ repeated string read_permissions = 17;
+ // The required permissions to write preference value.
+ repeated string write_permissions = 18;
// Target of an Intent
message ActionTarget {
@@ -108,6 +112,7 @@ message PreferenceValueProto {
oneof value {
bool boolean_value = 1;
int32 int_value = 2;
+ float float_value = 3;
}
}
@@ -116,6 +121,7 @@ message PreferenceValueDescriptorProto {
oneof type {
bool boolean_type = 1;
RangeValueProto range_value = 2;
+ bool float_type = 3;
}
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index eaa79266b194..91dec03bf2af 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -42,6 +42,7 @@ import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
import com.android.settingslib.graph.proto.TextProto
import com.android.settingslib.metadata.BooleanValue
+import com.android.settingslib.metadata.FloatPersistentPreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceHierarchy
@@ -390,7 +391,13 @@ fun PreferenceMetadata.toProto(
}
persistent = metadata.isPersistent(context)
if (persistent) {
- if (metadata is PersistentPreference<*>) sensitivityLevel = metadata.sensitivityLevel
+ if (metadata is PersistentPreference<*>) {
+ sensitivityLevel = metadata.sensitivityLevel
+ val readPermissions = metadata.getReadPermissions(context)
+ readPermissions.forEach { addReadPermissions(it) }
+ val writePermissions = metadata.getWritePermissions(context)
+ writePermissions.forEach { addWritePermissions(it) }
+ }
if (
flags.includeValue() &&
enabled &&
@@ -399,15 +406,13 @@ fun PreferenceMetadata.toProto(
metadata is PersistentPreference<*> &&
metadata.evalReadPermit(context, callingPid, callingUid) == ReadWritePermit.ALLOW
) {
+ val storage = metadata.storage(context)
value = preferenceValueProto {
when (metadata) {
- is BooleanValue ->
- metadata.storage(context).getBoolean(metadata.key)?.let {
- booleanValue = it
- }
- is RangeValue -> {
- metadata.storage(context).getInt(metadata.key)?.let { intValue = it }
- }
+ is BooleanValue -> storage.getBoolean(metadata.key)?.let { booleanValue = it }
+ is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
+ is FloatPersistentPreference ->
+ storage.getFloat(metadata.key)?.let { floatValue = it }
else -> {}
}
}
@@ -421,6 +426,7 @@ fun PreferenceMetadata.toProto(
max = metadata.getMaxValue(context)
step = metadata.getIncrementStep(context)
}
+ is FloatPersistentPreference -> floatType = true
else -> {}
}
}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d72ba0805db3..83c430488317 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -159,6 +159,12 @@ class PreferenceSetterApiHandler(
}
storage.setInt(key, intValue)
return PreferenceSetterResult.OK
+ } else if (value.hasFloatValue()) {
+ val floatValue = value.floatValue
+ val resultCode = metadata.checkWritePermit(floatValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
+ storage.setFloat(key, floatValue)
+ return PreferenceSetterResult.OK
}
} catch (e: Exception) {
return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index dee32d9ed80e..adbe77318353 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -35,8 +35,9 @@ val PreferenceScreenProto.rootOrNull
/** Kotlin DSL-style builder for [PreferenceScreenProto]. */
@JvmSynthetic
-inline fun preferenceScreenProto(init: PreferenceScreenProto.Builder.() -> Unit) =
- PreferenceScreenProto.newBuilder().also(init).build()
+inline fun preferenceScreenProto(
+ init: PreferenceScreenProto.Builder.() -> Unit
+): PreferenceScreenProto = PreferenceScreenProto.newBuilder().also(init).build()
/** Returns preference or null. */
val PreferenceOrGroupProto.preferenceOrNull
@@ -48,8 +49,9 @@ val PreferenceOrGroupProto.groupOrNull
/** Kotlin DSL-style builder for [PreferenceOrGroupProto]. */
@JvmSynthetic
-inline fun preferenceOrGroupProto(init: PreferenceOrGroupProto.Builder.() -> Unit) =
- PreferenceOrGroupProto.newBuilder().also(init).build()
+inline fun preferenceOrGroupProto(
+ init: PreferenceOrGroupProto.Builder.() -> Unit
+): PreferenceOrGroupProto = PreferenceOrGroupProto.newBuilder().also(init).build()
/** Returns preference or null. */
val PreferenceGroupProto.preferenceOrNull
@@ -57,8 +59,9 @@ val PreferenceGroupProto.preferenceOrNull
/** Kotlin DSL-style builder for [PreferenceGroupProto]. */
@JvmSynthetic
-inline fun preferenceGroupProto(init: PreferenceGroupProto.Builder.() -> Unit) =
- PreferenceGroupProto.newBuilder().also(init).build()
+inline fun preferenceGroupProto(
+ init: PreferenceGroupProto.Builder.() -> Unit
+): PreferenceGroupProto = PreferenceGroupProto.newBuilder().also(init).build()
/** Returns title or null. */
val PreferenceProto.titleOrNull
@@ -74,7 +77,7 @@ val PreferenceProto.actionTargetOrNull
/** Kotlin DSL-style builder for [PreferenceProto]. */
@JvmSynthetic
-inline fun preferenceProto(init: PreferenceProto.Builder.() -> Unit) =
+inline fun preferenceProto(init: PreferenceProto.Builder.() -> Unit): PreferenceProto =
PreferenceProto.newBuilder().also(init).build()
/** Returns intent or null. */
@@ -83,39 +86,42 @@ val ActionTarget.intentOrNull
/** Kotlin DSL-style builder for [ActionTarget]. */
@JvmSynthetic
-inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit) =
+inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit): ActionTarget =
ActionTarget.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [PreferenceValueProto]. */
@JvmSynthetic
-inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
- PreferenceValueProto.newBuilder().also(init).build()
+inline fun preferenceValueProto(
+ init: PreferenceValueProto.Builder.() -> Unit
+): PreferenceValueProto = PreferenceValueProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
@JvmSynthetic
-inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
- PreferenceValueDescriptorProto.newBuilder().also(init).build()
+inline fun preferenceValueDescriptorProto(
+ init: PreferenceValueDescriptorProto.Builder.() -> Unit
+): PreferenceValueDescriptorProto = PreferenceValueDescriptorProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [RangeValueProto]. */
@JvmSynthetic
-inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit): RangeValueProto =
RangeValueProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [TextProto]. */
@JvmSynthetic
-inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
+inline fun textProto(init: TextProto.Builder.() -> Unit): TextProto =
+ TextProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [IntentProto]. */
@JvmSynthetic
-inline fun intentProto(init: IntentProto.Builder.() -> Unit) =
+inline fun intentProto(init: IntentProto.Builder.() -> Unit): IntentProto =
IntentProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [BundleProto]. */
@JvmSynthetic
-inline fun bundleProto(init: BundleProto.Builder.() -> Unit) =
+inline fun bundleProto(init: BundleProto.Builder.() -> Unit): BundleProto =
BundleProto.newBuilder().also(init).build()
/** Kotlin DSL-style builder for [BundleValue]. */
@JvmSynthetic
-inline fun bundleValueProto(init: BundleValue.Builder.() -> Unit) =
+inline fun bundleValueProto(init: BundleValue.Builder.() -> Unit): BundleValue =
BundleValue.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index adc4f316aca9..bc4f1f942dc0 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -167,6 +167,7 @@ public class IllustrationPreference extends Preference implements GroupSectionDi
if (mLottieDynamicColor) {
LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
}
+ LottieColorUtils.applyMaterialColor(getContext(), illustrationView);
if (mOnBindListener != null) {
mOnBindListener.onBind(illustrationView);
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 98a72909b276..4421424c0e39 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -21,14 +21,14 @@ import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import com.android.settingslib.color.R;
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.widget.theme.R;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieProperty;
import com.airbnb.lottie.model.KeyPath;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -37,52 +37,97 @@ import java.util.Map;
*/
public class LottieColorUtils {
private static final Map<String, Integer> DARK_TO_LIGHT_THEME_COLOR_MAP;
+ private static final Map<String, Integer> MATERIAL_COLOR_MAP;
static {
- HashMap<String, Integer> map = new HashMap<>();
- map.put(
- ".grey200",
- R.color.settingslib_color_grey800);
- map.put(
- ".grey600",
- R.color.settingslib_color_grey400);
- map.put(
- ".grey800",
- R.color.settingslib_color_grey300);
- map.put(
- ".grey900",
- R.color.settingslib_color_grey50);
- map.put(
- ".red100",
- R.color.settingslib_color_red500);
- map.put(
- ".red200",
- R.color.settingslib_color_red500);
- map.put(
- ".red400",
- R.color.settingslib_color_red600);
- map.put(
- ".black",
- android.R.color.white);
- map.put(
- ".blue200",
- R.color.settingslib_color_blue700);
- map.put(
- ".blue400",
- R.color.settingslib_color_blue600);
- map.put(
- ".green100",
- R.color.settingslib_color_green500);
- map.put(
- ".green200",
- R.color.settingslib_color_green500);
- map.put(
- ".green400",
- R.color.settingslib_color_green600);
- map.put(
- ".cream",
- R.color.settingslib_color_charcoal);
- DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
+ DARK_TO_LIGHT_THEME_COLOR_MAP = Map.ofEntries(
+ Map.entry(".grey200",
+ com.android.settingslib.color.R.color.settingslib_color_grey800),
+ Map.entry(".grey600",
+ com.android.settingslib.color.R.color.settingslib_color_grey400),
+ Map.entry(".grey800",
+ com.android.settingslib.color.R.color.settingslib_color_grey300),
+ Map.entry(".grey900",
+ com.android.settingslib.color.R.color.settingslib_color_grey50),
+ Map.entry(".red100",
+ com.android.settingslib.color.R.color.settingslib_color_red500),
+ Map.entry(".red200",
+ com.android.settingslib.color.R.color.settingslib_color_red500),
+ Map.entry(".red400",
+ com.android.settingslib.color.R.color.settingslib_color_red600),
+ Map.entry(".black",
+ android.R.color.white),
+ Map.entry(".blue200",
+ com.android.settingslib.color.R.color.settingslib_color_blue700),
+ Map.entry(".blue400",
+ com.android.settingslib.color.R.color.settingslib_color_blue600),
+ Map.entry(".green100",
+ com.android.settingslib.color.R.color.settingslib_color_green500),
+ Map.entry(".green200",
+ com.android.settingslib.color.R.color.settingslib_color_green500),
+ Map.entry(".green400",
+ com.android.settingslib.color.R.color.settingslib_color_green600),
+ Map.entry(".cream",
+ com.android.settingslib.color.R.color.settingslib_color_charcoal));
+
+ MATERIAL_COLOR_MAP = Map.ofEntries(
+ Map.entry(".primary", R.color.settingslib_materialColorPrimary),
+ Map.entry(".onPrimary", R.color.settingslib_materialColorOnPrimary),
+ Map.entry(".primaryContainer", R.color.settingslib_materialColorPrimaryContainer),
+ Map.entry(".onPrimaryContainer",
+ R.color.settingslib_materialColorOnPrimaryContainer),
+ Map.entry(".primaryInverse", R.color.settingslib_materialColorPrimaryInverse),
+ Map.entry(".primaryFixed", R.color.settingslib_materialColorPrimaryFixed),
+ Map.entry(".primaryFixedDim", R.color.settingslib_materialColorPrimaryFixedDim),
+ Map.entry(".onPrimaryFixed", R.color.settingslib_materialColorOnPrimaryFixed),
+ Map.entry(".onPrimaryFixedVariant",
+ R.color.settingslib_materialColorOnPrimaryFixedVariant),
+ Map.entry(".secondary", R.color.settingslib_materialColorSecondary),
+ Map.entry(".onSecondary", R.color.settingslib_materialColorOnSecondary),
+ Map.entry(".secondaryContainer",
+ R.color.settingslib_materialColorSecondaryContainer),
+ Map.entry(".onSecondaryContainer",
+ R.color.settingslib_materialColorOnSecondaryContainer),
+ Map.entry(".secondaryFixed", R.color.settingslib_materialColorSecondaryFixed),
+ Map.entry(".secondaryFixedDim", R.color.settingslib_materialColorSecondaryFixedDim),
+ Map.entry(".onSecondaryFixed", R.color.settingslib_materialColorOnSecondaryFixed),
+ Map.entry(".onSecondaryFixedVariant",
+ R.color.settingslib_materialColorOnSecondaryFixedVariant),
+ Map.entry(".tertiary", R.color.settingslib_materialColorTertiary),
+ Map.entry(".onTertiary", R.color.settingslib_materialColorOnTertiary),
+ Map.entry(".tertiaryContainer", R.color.settingslib_materialColorTertiaryContainer),
+ Map.entry(".onTertiaryContainer",
+ R.color.settingslib_materialColorOnTertiaryContainer),
+ Map.entry(".tertiaryFixed", R.color.settingslib_materialColorTertiaryFixed),
+ Map.entry(".tertiaryFixedDim", R.color.settingslib_materialColorTertiaryFixedDim),
+ Map.entry(".onTertiaryFixed", R.color.settingslib_materialColorOnTertiaryFixed),
+ Map.entry(".onTertiaryFixedVariant",
+ R.color.settingslib_materialColorOnTertiaryFixedVariant),
+ Map.entry(".error", R.color.settingslib_materialColorError),
+ Map.entry(".onError", R.color.settingslib_materialColorOnError),
+ Map.entry(".errorContainer", R.color.settingslib_materialColorErrorContainer),
+ Map.entry(".onErrorContainer", R.color.settingslib_materialColorOnErrorContainer),
+ Map.entry(".outline", R.color.settingslib_materialColorOutline),
+ Map.entry(".outlineVariant", R.color.settingslib_materialColorOutlineVariant),
+ Map.entry(".background", R.color.settingslib_materialColorBackground),
+ Map.entry(".onBackground", R.color.settingslib_materialColorOnBackground),
+ Map.entry(".surface", R.color.settingslib_materialColorSurface),
+ Map.entry(".onSurface", R.color.settingslib_materialColorOnSurface),
+ Map.entry(".surfaceVariant", R.color.settingslib_materialColorSurfaceVariant),
+ Map.entry(".onSurfaceVariant", R.color.settingslib_materialColorOnSurfaceVariant),
+ Map.entry(".surfaceInverse", R.color.settingslib_materialColorSurfaceInverse),
+ Map.entry(".onSurfaceInverse", R.color.settingslib_materialColorOnSurfaceInverse),
+ Map.entry(".surfaceBright", R.color.settingslib_materialColorSurfaceBright),
+ Map.entry(".surfaceDim", R.color.settingslib_materialColorSurfaceDim),
+ Map.entry(".surfaceContainer", R.color.settingslib_materialColorSurfaceContainer),
+ Map.entry(".surfaceContainerLow",
+ R.color.settingslib_materialColorSurfaceContainerLow),
+ Map.entry(".surfaceContainerLowest",
+ R.color.settingslib_materialColorSurfaceContainerLowest),
+ Map.entry(".surfaceContainerHigh",
+ R.color.settingslib_materialColorSurfaceContainerHigh),
+ Map.entry(".surfaceContainerHighest",
+ R.color.settingslib_materialColorSurfaceContainerHighest));
}
private LottieColorUtils() {
@@ -108,4 +153,20 @@ public class LottieColorUtils {
frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
}
}
+
+ /** Applies material colors. */
+ public static void applyMaterialColor(@NonNull Context context,
+ @NonNull LottieAnimationView lottieAnimationView) {
+ if (!SettingsThemeHelper.isExpressiveTheme(context)) {
+ return;
+ }
+
+ for (String key : MATERIAL_COLOR_MAP.keySet()) {
+ final int color = context.getColor(MATERIAL_COLOR_MAP.get(key));
+ lottieAnimationView.addValueCallback(
+ new KeyPath("**", key, "**"),
+ LottieProperty.COLOR_FILTER,
+ frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+ }
+ }
}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index d3a731688c24..3dd6c47833fd 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -201,3 +201,6 @@ interface RangeValue : ValueDescriptor {
override fun isValidValue(context: Context, index: Int) =
index in getMinValue(context)..getMaxValue(context)
}
+
+/** A persistent preference that has a float value. */
+interface FloatPersistentPreference : PersistentPreference<Float>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
index 03a2101544be..218983a55e1b 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
@@ -31,7 +31,6 @@ import androidx.preference.CheckBoxPreference;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.selector.R;
-import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags;
/**
* Selector preference (checkbox or radio button) with an optional additional widget.
@@ -180,10 +179,8 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference {
: getContext().getString(R.string.settings_label));
}
- if (Flags.allowSetTitleMaxLines()) {
- TextView title = (TextView) holder.findViewById(android.R.id.title);
- title.setMaxLines(mTitleMaxLines);
- }
+ TextView title = (TextView) holder.findViewById(android.R.id.title);
+ title.setMaxLines(mTitleMaxLines);
}
/**
@@ -244,16 +241,12 @@ public class SelectorWithWidgetPreference extends CheckBoxPreference {
setLayoutResource(R.layout.preference_selector_with_widget);
setIconSpaceReserved(false);
- if (Flags.allowSetTitleMaxLines()) {
- final TypedArray a =
- context.obtainStyledAttributes(
- attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr,
- defStyleRes);
- mTitleMaxLines =
- a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines,
- DEFAULT_MAX_LINES);
- a.recycle();
- }
+ final TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.SelectorWithWidgetPreference, defStyleAttr, defStyleRes);
+ mTitleMaxLines =
+ a.getInt(R.styleable.SelectorWithWidgetPreference_titleMaxLines, DEFAULT_MAX_LINES);
+ a.recycle();
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 1a043d5015b2..cc996c5a2120 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -97,6 +97,7 @@ flag {
name: "settings_catalyst"
namespace: "android_settings"
description: "Settings catalyst project migration"
+ is_exported: true
bug: "323791114"
is_exported: true
}
@@ -106,6 +107,7 @@ flag {
is_fixed_read_only: true
namespace: "android_settings"
description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
+ is_exported: true
bug: "375193223"
is_exported: true
}
@@ -197,3 +199,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "disable_audio_sharing_auto_pick_fallback_in_ui"
+ namespace: "cross_device_experiences"
+ description: "Do not auto pick audio sharing fallback device in UI"
+ bug: "383469911"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e1929b725a58..6cf9e83ef342 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -229,6 +229,8 @@
<string name="bluetooth_hearing_aid_right_active">Active (right only)</string>
<!-- Connected device settings. Message when the left-side and right-side hearing aids device are active. [CHAR LIMIT=NONE] -->
<string name="bluetooth_hearing_aid_left_and_right_active">Active (left and right)</string>
+ <!-- Connected device settings.: Message when changing remote ambient state failed. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_hearing_device_ambient_error">Couldn\u2019t update surroundings</string>
<!-- Connected devices settings. Message when Bluetooth is connected and active for media only, showing remote device status and battery level. [CHAR LIMIT=NONE] -->
<string name="bluetooth_active_media_only_battery_level">Active (media only). <xliff:g id="battery_level_as_percentage">%1$s</xliff:g> battery.</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java
new file mode 100644
index 000000000000..881a97bfadcd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUi.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import android.bluetooth.BluetoothDevice;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/** Interface for the ambient volume UI. */
+public interface AmbientVolumeUi {
+
+ /** Interface definition for a callback to be invoked when event happens in AmbientVolumeUi. */
+ interface AmbientVolumeUiListener {
+ /** Called when the expand icon is clicked. */
+ void onExpandIconClick();
+
+ /** Called when the ambient volume icon is clicked. */
+ void onAmbientVolumeIconClick();
+
+ /** Called when the slider of the specified side is changed. */
+ void onSliderValueChange(int side, int value);
+ };
+
+ /** The rotation degree of the expand icon when the UI is in collapsed mode. */
+ float ROTATION_COLLAPSED = 0f;
+ /** The rotation degree of the expand icon when the UI is in expanded mode. */
+ float ROTATION_EXPANDED = 180f;
+
+ /**
+ * The default ambient volume level for hearing device ambient volume icon
+ *
+ * <p> This icon visually represents the current ambient volume. It displays separate
+ * levels for the left and right sides, each with 5 levels ranging from 0 to 4.
+ *
+ * <p> To represent the combined left/right levels with a single value, the following
+ * calculation is used:
+ * finalLevel = (leftLevel * 5) + rightLevel
+ * For example:
+ * <ul>
+ * <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
+ * <li>If both left and right levels are 0, the final level will be 0</li>
+ * <li>If both left and right levels are 4, the final level will be 24</li>
+ * </ul>
+ */
+ int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
+ /**
+ * The minimum ambient volume level for hearing device ambient volume icon
+ *
+ * @see #AMBIENT_VOLUME_LEVEL_DEFAULT
+ */
+ int AMBIENT_VOLUME_LEVEL_MIN = 0;
+ /**
+ * The maximum ambient volume level for hearing device ambient volume icon
+ *
+ * @see #AMBIENT_VOLUME_LEVEL_DEFAULT
+ */
+ int AMBIENT_VOLUME_LEVEL_MAX = 24;
+
+ /**
+ * Ths side identifier for slider in collapsed mode which can unified control the ambient
+ * volume of all devices in the same set.
+ */
+ int SIDE_UNIFIED = 999;
+
+ /** All valid side of the sliders in the UI. */
+ List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
+
+ /** Sets if the UI is visible. */
+ void setVisible(boolean visible);
+
+ /**
+ * Sets if the UI is expandable between expanded and collapsed mode.
+ *
+ * <p> If the UI is not expandable, it implies the UI will always stay in collapsed mode
+ */
+ void setExpandable(boolean expandable);
+
+ /** @return if the UI is expandable. */
+ boolean isExpandable();
+
+ /** Sets if the UI is in expanded mode. */
+ void setExpanded(boolean expanded);
+
+ /** @return if the UI is in expanded mode. */
+ boolean isExpanded();
+
+ /**
+ * Sets if the UI is capable to mute the ambient of the remote device.
+ *
+ * <p> If the value is {@code false}, it implies the remote device ambient will always be
+ * unmute and can not be mute from the UI
+ */
+ void setMutable(boolean mutable);
+
+ /** @return if the UI is capable to mute the ambient of remote device. */
+ boolean isMutable();
+
+ /** Sets if the UI shows mute state. */
+ void setMuted(boolean muted);
+
+ /** @return if the UI shows mute state */
+ boolean isMuted();
+
+ /**
+ * Sets listener on the UI.
+ *
+ * @see AmbientVolumeUiListener
+ */
+ void setListener(@Nullable AmbientVolumeUiListener listener);
+
+ /**
+ * Sets up sliders in the UI.
+ *
+ * <p> For each side of device, the UI should hava a corresponding slider to control it's
+ * ambient volume.
+ * <p> For all devices in the same set, the UI should have a slider to control all devices'
+ * ambient volume at once.
+ * @param sideToDeviceMap the side and device mapping of all devices in the same set
+ */
+ void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap);
+
+ /**
+ * Sets if the slider is enabled.
+ *
+ * @param side the side of the slider
+ * @param enabled the enabled state
+ */
+ void setSliderEnabled(int side, boolean enabled);
+
+ /**
+ * Sets the slider value.
+ *
+ * @param side the side of the slider
+ * @param value the ambient value
+ */
+ void setSliderValue(int side, int value);
+
+ /**
+ * Sets the slider's minimum and maximum value.
+ *
+ * @param side the side of the slider
+ * @param min the minimum ambient value
+ * @param max the maximum ambient value
+ */
+ void setSliderRange(int side, int min, int max);
+
+ /** Updates the UI according to current state. */
+ void updateLayout();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
new file mode 100644
index 000000000000..ce392b12516f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settingslib.bluetooth.AmbientVolumeUi.SIDE_UNIFIED;
+import static com.android.settingslib.bluetooth.AmbientVolumeUi.VALID_SIDES;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.ArraySet;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.R;
+import com.android.settingslib.utils.ThreadUtils;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+import java.util.Map;
+import java.util.Set;
+
+/** This class controls ambient volume UI with local and remote ambient data. */
+public class AmbientVolumeUiController implements
+ HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener,
+ AmbientVolumeController.AmbientVolumeControlCallback,
+ AmbientVolumeUi.AmbientVolumeUiListener, BluetoothCallback, CachedBluetoothDevice.Callback {
+
+ private static final boolean DEBUG = true;
+ private static final String TAG = "AmbientVolumeUiController";
+
+ private final Context mContext;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothEventManager mEventManager;
+ private final AmbientVolumeUi mAmbientLayout;
+ private final AmbientVolumeController mVolumeController;
+ private final HearingDeviceLocalDataManager mLocalDataManager;
+
+ private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
+ private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
+ private CachedBluetoothDevice mCachedDevice;
+ private boolean mShowUiWhenLocalDataExist = true;
+
+ public AmbientVolumeUiController(@NonNull Context context,
+ @NonNull LocalBluetoothManager bluetoothManager,
+ @NonNull AmbientVolumeUi ambientLayout) {
+ mContext = context;
+ mProfileManager = bluetoothManager.getProfileManager();
+ mEventManager = bluetoothManager.getEventManager();
+ mAmbientLayout = ambientLayout;
+ mAmbientLayout.setListener(this);
+ mVolumeController = new AmbientVolumeController(mProfileManager, this);
+ mLocalDataManager = new HearingDeviceLocalDataManager(context);
+ mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
+ ThreadUtils.getBackgroundExecutor());
+ }
+
+ @VisibleForTesting
+ public AmbientVolumeUiController(@NonNull Context context,
+ @NonNull LocalBluetoothManager bluetoothManager,
+ @NonNull AmbientVolumeUi ambientLayout,
+ @NonNull AmbientVolumeController volumeController,
+ @NonNull HearingDeviceLocalDataManager localDataManager) {
+ mContext = context;
+ mProfileManager = bluetoothManager.getProfileManager();
+ mEventManager = bluetoothManager.getEventManager();
+ mAmbientLayout = ambientLayout;
+ mVolumeController = volumeController;
+ mLocalDataManager = localDataManager;
+ }
+
+
+ @Override
+ public void onDeviceLocalDataChange(@NonNull String address,
+ @Nullable HearingDeviceLocalDataManager.Data data) {
+ if (data == null) {
+ // The local data is removed because the device is unpaired, do nothing
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceLocalDataChange, address:" + address + ", data:" + data);
+ }
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ if (device.getAnonymizedAddress().equals(address)) {
+ postOnMainThread(() -> loadLocalDataToUi(device));
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onVolumeControlServiceConnected() {
+ mCachedDevices.forEach(device -> mVolumeController.registerCallback(
+ ThreadUtils.getBackgroundExecutor(), device.getDevice()));
+ }
+
+ @Override
+ public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
+ if (DEBUG) {
+ Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
+ }
+ HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+ final boolean expanded = mAmbientLayout.isExpanded();
+ final boolean isInitiatedFromUi = (expanded && data.ambient() == gainSettings)
+ || (!expanded && data.groupAmbient() == gainSettings);
+ if (isInitiatedFromUi) {
+ // The change is initiated from UI, no need to update UI
+ return;
+ }
+
+ // We have to check if we need to expand the controls by getting all remote
+ // device's ambient value, delay for a while to wait all remote devices update
+ // to the latest value to avoid unnecessary expand action.
+ postDelayedOnMainThread(this::refresh, 1200L);
+ }
+
+ @Override
+ public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
+ if (DEBUG) {
+ Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
+ }
+ final boolean muted = mAmbientLayout.isMuted();
+ boolean isInitiatedFromUi = (muted && mute == MUTE_MUTED)
+ || (!muted && mute == MUTE_NOT_MUTED);
+ if (isInitiatedFromUi) {
+ // The change is initiated from UI, no need to update UI
+ return;
+ }
+
+ // We have to check if we need to mute the devices by getting all remote
+ // device's mute state, delay for a while to wait all remote devices update
+ // to the latest value.
+ postDelayedOnMainThread(this::refresh, 1200L);
+ }
+
+ @Override
+ public void onCommandFailed(@NonNull BluetoothDevice device) {
+ Log.w(TAG, "onCommandFailed, device:" + device);
+ postOnMainThread(() -> {
+ showErrorToast(R.string.bluetooth_hearing_device_ambient_error);
+ refresh();
+ });
+ }
+
+ @Override
+ public void onExpandIconClick() {
+ mSideToDeviceMap.forEach((s, d) -> {
+ if (!mAmbientLayout.isMuted()) {
+ // Apply previous collapsed/expanded volume to remote device
+ HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(d);
+ int volume = mAmbientLayout.isExpanded()
+ ? data.ambient() : data.groupAmbient();
+ mVolumeController.setAmbient(d, volume);
+ }
+ // Update new value to local data
+ mLocalDataManager.updateAmbientControlExpanded(d,
+ mAmbientLayout.isExpanded());
+ });
+ mLocalDataManager.flush();
+ }
+
+ @Override
+ public void onAmbientVolumeIconClick() {
+ if (!mAmbientLayout.isMuted()) {
+ loadLocalDataToUi();
+ }
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ mVolumeController.setMuted(device, mAmbientLayout.isMuted());
+ }
+ }
+
+ @Override
+ public void onSliderValueChange(int side, int value) {
+ if (DEBUG) {
+ Log.d(TAG, "onSliderValueChange: side=" + side + ", value=" + value);
+ }
+ setVolumeIfValid(side, value);
+
+ Runnable setAmbientRunnable = () -> {
+ if (side == SIDE_UNIFIED) {
+ mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
+ } else {
+ final BluetoothDevice device = mSideToDeviceMap.get(side);
+ mVolumeController.setAmbient(device, value);
+ }
+ };
+
+ if (mAmbientLayout.isMuted()) {
+ // User drag on the volume slider when muted. Unmute the devices first.
+ mAmbientLayout.setMuted(false);
+
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ mVolumeController.setMuted(device, false);
+ }
+ // Restore the value before muted
+ loadLocalDataToUi();
+ // Delay set ambient on remote device since the immediately sequential command
+ // might get failed sometimes
+ postDelayedOnMainThread(setAmbientRunnable, 1000L);
+ } else {
+ setAmbientRunnable.run();
+ }
+ }
+
+ @Override
+ public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
+ int state, int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
+ && state == BluetoothProfile.STATE_CONNECTED
+ && mCachedDevices.contains(cachedDevice)) {
+ // After VCP connected, AICS may not ready yet and still return invalid value, delay
+ // a while to wait AICS ready as a workaround
+ postDelayedOnMainThread(this::refresh, 1000L);
+ }
+ }
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ mCachedDevices.forEach(device -> {
+ device.unregisterCallback(this);
+ mVolumeController.unregisterCallback(device.getDevice());
+ });
+ postOnMainThread(()-> {
+ loadDevice(mCachedDevice);
+ ThreadUtils.postOnBackgroundThread(()-> {
+ mCachedDevices.forEach(device -> {
+ device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+ mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+ device.getDevice());
+ });
+ });
+ });
+ }
+
+ /**
+ * Registers callbacks and listeners, this should be called when needs to start listening to
+ * events.
+ */
+ public void start() {
+ mEventManager.registerCallback(this);
+ mLocalDataManager.start();
+ mCachedDevices.forEach(device -> {
+ device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+ mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+ device.getDevice());
+ });
+ }
+
+ /**
+ * Unregisters callbacks and listeners, this should be called when no longer needs to listen to
+ * events.
+ */
+ public void stop() {
+ mEventManager.unregisterCallback(this);
+ mLocalDataManager.stop();
+ mCachedDevices.forEach(device -> {
+ device.unregisterCallback(this);
+ mVolumeController.unregisterCallback(device.getDevice());
+ });
+ }
+
+ /**
+ * Loads all devices in the same set with {@code cachedDevice} and create corresponding sliders.
+ *
+ * <p>If the devices has valid ambient control points, the ambient volume UI will be visible.
+ * @param cachedDevice the remote device
+ */
+ public void loadDevice(CachedBluetoothDevice cachedDevice) {
+ if (DEBUG) {
+ Log.d(TAG, "loadDevice, device=" + cachedDevice);
+ }
+ mCachedDevice = cachedDevice;
+ mSideToDeviceMap.clear();
+ mCachedDevices.clear();
+ boolean deviceSupportVcp =
+ cachedDevice != null && cachedDevice.getProfiles().stream().anyMatch(
+ p -> p instanceof VolumeControlProfile);
+ if (!deviceSupportVcp) {
+ mAmbientLayout.setVisible(false);
+ return;
+ }
+
+ // load devices in the same set
+ if (VALID_SIDES.contains(cachedDevice.getDeviceSide())
+ && cachedDevice.getBondState() == BOND_BONDED) {
+ mSideToDeviceMap.put(cachedDevice.getDeviceSide(), cachedDevice.getDevice());
+ mCachedDevices.add(cachedDevice);
+ }
+ for (CachedBluetoothDevice memberDevice : cachedDevice.getMemberDevice()) {
+ if (VALID_SIDES.contains(memberDevice.getDeviceSide())
+ && memberDevice.getBondState() == BOND_BONDED) {
+ mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
+ mCachedDevices.add(memberDevice);
+ }
+ }
+
+ mAmbientLayout.setExpandable(mSideToDeviceMap.size() > 1);
+ mAmbientLayout.setupSliders(mSideToDeviceMap);
+ refresh();
+ }
+
+ /** Refreshes the ambient volume UI. */
+ public void refresh() {
+ if (isAmbientControlAvailable()) {
+ mAmbientLayout.setVisible(true);
+ loadRemoteDataToUi();
+ } else {
+ mAmbientLayout.setVisible(false);
+ }
+ }
+
+ /** Sets if the ambient volume UI should be visible when local ambient data exist. */
+ public void setShowUiWhenLocalDataExist(boolean shouldShow) {
+ mShowUiWhenLocalDataExist = shouldShow;
+ }
+
+ /** Updates the ambient sliders according to current state. */
+ private void updateSliderUi() {
+ boolean isAnySliderEnabled = false;
+ for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
+ final int side = entry.getKey();
+ final BluetoothDevice device = entry.getValue();
+ final boolean enabled = isDeviceConnectedToVcp(device)
+ && mVolumeController.isAmbientControlAvailable(device);
+ isAnySliderEnabled |= enabled;
+ mAmbientLayout.setSliderEnabled(side, enabled);
+ }
+ mAmbientLayout.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
+ mAmbientLayout.updateLayout();
+ }
+
+ /** Sets the ambient to the corresponding control slider. */
+ private void setVolumeIfValid(int side, int volume) {
+ if (volume == INVALID_VOLUME) {
+ return;
+ }
+ mAmbientLayout.setSliderValue(side, volume);
+ // Update new value to local data
+ if (side == SIDE_UNIFIED) {
+ mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
+ } else {
+ mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
+ }
+ mLocalDataManager.flush();
+ }
+
+ private void loadLocalDataToUi() {
+ mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
+ }
+
+ private void loadLocalDataToUi(BluetoothDevice device) {
+ final HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+ if (DEBUG) {
+ Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
+ }
+ if (isDeviceConnectedToVcp(device) && !mAmbientLayout.isMuted()) {
+ final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
+ setVolumeIfValid(side, data.ambient());
+ setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
+ }
+ setAmbientControlExpanded(data.ambientControlExpanded());
+ updateSliderUi();
+ }
+
+ private void loadRemoteDataToUi() {
+ BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
+ AmbientVolumeController.RemoteAmbientState leftState =
+ mVolumeController.refreshAmbientState(leftDevice);
+ BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
+ AmbientVolumeController.RemoteAmbientState rightState =
+ mVolumeController.refreshAmbientState(rightDevice);
+ if (DEBUG) {
+ Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
+ }
+ mSideToDeviceMap.forEach((side, device) -> {
+ int ambientMax = mVolumeController.getAmbientMax(device);
+ int ambientMin = mVolumeController.getAmbientMin(device);
+ if (ambientMin != ambientMax) {
+ mAmbientLayout.setSliderRange(side, ambientMin, ambientMax);
+ mAmbientLayout.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
+ }
+ });
+
+ // Update ambient volume
+ final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
+ final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
+ if (mAmbientLayout.isExpanded()) {
+ setVolumeIfValid(SIDE_LEFT, leftAmbient);
+ setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+ } else {
+ if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
+ && rightAmbient != INVALID_VOLUME) {
+ setVolumeIfValid(SIDE_LEFT, leftAmbient);
+ setVolumeIfValid(SIDE_RIGHT, rightAmbient);
+ setAmbientControlExpanded(true);
+ } else {
+ int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
+ setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
+ }
+ }
+ // Initialize local data between side and group value
+ initLocalAmbientDataIfNeeded();
+
+ // Update mute state
+ boolean mutable = true;
+ boolean muted = true;
+ if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
+ mutable &= leftState.isMutable();
+ muted &= leftState.isMuted();
+ }
+ if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
+ mutable &= rightState.isMutable();
+ muted &= rightState.isMuted();
+ }
+ mAmbientLayout.setMutable(mutable);
+ mAmbientLayout.setMuted(muted);
+
+ // Ensure remote device mute state is synced
+ syncMuteStateIfNeeded(leftDevice, leftState, muted);
+ syncMuteStateIfNeeded(rightDevice, rightState, muted);
+
+ updateSliderUi();
+ }
+
+ private void setAmbientControlExpanded(boolean expanded) {
+ mAmbientLayout.setExpanded(expanded);
+ mSideToDeviceMap.forEach((s, d) -> {
+ // Update new value to local data
+ mLocalDataManager.updateAmbientControlExpanded(d, expanded);
+ });
+ mLocalDataManager.flush();
+ }
+
+ /** Checks if any device in the same set has valid ambient control points */
+ private boolean isAmbientControlAvailable() {
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ if (mShowUiWhenLocalDataExist) {
+ // Found local ambient data
+ if (mLocalDataManager.get(device).hasAmbientData()) {
+ return true;
+ }
+ }
+ // Found remote ambient control points
+ if (mVolumeController.isAmbientControlAvailable(device)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void initLocalAmbientDataIfNeeded() {
+ int smallerVolumeAmongGroup = Integer.MAX_VALUE;
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+ if (data.ambient() != INVALID_VOLUME) {
+ smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
+ } else if (data.groupAmbient() != INVALID_VOLUME) {
+ // Initialize side ambient from group ambient value
+ mLocalDataManager.updateAmbient(device, data.groupAmbient());
+ }
+ }
+ if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
+ for (BluetoothDevice device : mSideToDeviceMap.values()) {
+ HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(device);
+ if (data.groupAmbient() == INVALID_VOLUME) {
+ // Initialize group ambient from smaller side ambient value
+ mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
+ }
+ }
+ }
+ mLocalDataManager.flush();
+ }
+
+ private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
+ @Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
+ if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
+ if (state.isMuted() != muted) {
+ mVolumeController.setMuted(device, muted);
+ }
+ }
+ }
+
+ private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
+ return device != null && device.isConnected()
+ && mProfileManager.getVolumeControlProfile().getConnectionStatus(device)
+ == BluetoothProfile.STATE_CONNECTED;
+ }
+
+ private void postOnMainThread(Runnable runnable) {
+ mContext.getMainThreadHandler().post(runnable);
+ }
+
+ private void postDelayedOnMainThread(Runnable runnable, long delay) {
+ mContext.getMainThreadHandler().postDelayed(runnable, delay);
+ }
+
+ private void showErrorToast(int stringResId) {
+ Toast.makeText(mContext, stringResId, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 429e4c958f05..0c642d7b8f98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -656,7 +656,8 @@ public class BluetoothUtils {
@WorkerThread
public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) {
return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing())
- || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver()));
+ || (context != null && Flags.audioSharingDeveloperOption()
+ && getAudioSharingPreviewValue(context.getContentResolver()));
}
/** Returns if the le audio sharing is enabled. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
index 6725558cd2bd..3cd37320243f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
@@ -148,6 +148,14 @@ public class HearingDeviceLocalDataManager {
}
}
+ /** Flushes the data into Settings . */
+ public synchronized void flush() {
+ if (!mIsStarted) {
+ return;
+ }
+ putAmbientVolumeSettings();
+ }
+
/**
* Puts the local data of the corresponding hearing device.
*
@@ -274,9 +282,6 @@ public class HearingDeviceLocalDataManager {
notifyIfDataChanged(mAddrToDataMap, updatedAddrToDataMap);
mAddrToDataMap.clear();
mAddrToDataMap.putAll(updatedAddrToDataMap);
- if (DEBUG) {
- Log.v(TAG, "getLocalDataFromSettings, " + mAddrToDataMap + ", manager: " + this);
- }
}
}
@@ -287,12 +292,10 @@ public class HearingDeviceLocalDataManager {
builder.append(KEY_ADDR).append("=").append(entry.getKey());
builder.append(entry.getValue().toSettingsFormat()).append(";");
}
- if (DEBUG) {
- Log.v(TAG, "putAmbientVolumeSettings, " + builder + ", manager: " + this);
- }
- Settings.Global.putStringForUser(mContext.getContentResolver(),
- LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(),
- UserHandle.USER_SYSTEM);
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Settings.Global.putStringForUser(mContext.getContentResolver(),
+ LOCAL_AMBIENT_VOLUME_SETTINGS, builder.toString(), UserHandle.USER_SYSTEM);
+ });
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index b52ed42d567f..2c99a2d4818c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -101,6 +101,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
public @interface BroadcastState {}
private static final String SETTINGS_PKG = "com.android.settings";
+ private static final String SYSUI_PKG = "com.android.systemui";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -216,6 +217,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
setLatestBroadcastId(broadcastId);
setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
+ notifyBroadcastStateChange(BROADCAST_STATE_ON);
}
@Override
@@ -232,7 +234,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
}
setLatestBluetoothLeBroadcastMetadata(metadata);
- notifyBroadcastStateChange(BROADCAST_STATE_ON);
}
@Override
@@ -1247,8 +1248,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
private void notifyBroadcastStateChange(@BroadcastState int state) {
- if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
- Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
+ String packageName = mContext.getPackageName();
+ if (!packageName.equals(SETTINGS_PKG) && !packageName.equals(SYSUI_PKG)) {
+ Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings or SystemUI.");
return;
}
if (isWorkProfile(mContext)) {
@@ -1257,8 +1259,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
- intent.setPackage(mContext.getPackageName());
- Log.d(TAG, "notifyBroadcastStateChange for state = " + state);
+ intent.setPackage(SETTINGS_PKG);
+ Log.d(TAG, "notifyBroadcastStateChange for state = " + state + " by pkg = " + packageName);
mContext.sendBroadcast(intent);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index cf452314163f..c9aac91f1320 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -21,6 +21,7 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_
import android.annotation.IntDef;
import android.annotation.MainThread;
+import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -1643,7 +1644,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
CharSequence appLabel = "";
ApplicationInfo appInfo = null;
try {
- int userId = UserHandle.getUserId(UserHandle.USER_CURRENT);
+ int userId = ActivityManager.getCurrentUser();
appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to get app info", e);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java
new file mode 100644
index 000000000000..8b606e299971
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeUiControllerTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link AmbientVolumeUiController}. */
+@RunWith(RobolectricTestRunner.class)
+public class AmbientVolumeUiControllerTest {
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ private static final String TEST_ADDRESS = "00:00:00:00:11";
+ private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
+
+ @Mock
+ LocalBluetoothManager mBluetoothManager;
+ @Mock
+ LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ BluetoothEventManager mEventManager;
+ @Mock
+ VolumeControlProfile mVolumeControlProfile;
+ @Mock
+ AmbientVolumeUi mAmbientLayout;
+ @Mock
+ private AmbientVolumeController mVolumeController;
+ @Mock
+ private HearingDeviceLocalDataManager mLocalDataManager;
+ @Mock
+ private CachedBluetoothDevice mCachedDevice;
+ @Mock
+ private CachedBluetoothDevice mCachedMemberDevice;
+ @Mock
+ private BluetoothDevice mDevice;
+ @Mock
+ private BluetoothDevice mMemberDevice;
+ @Mock
+ private Handler mTestHandler;
+
+ @Spy
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private AmbientVolumeUiController mController;
+
+ @Before
+ public void setUp() {
+ when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
+
+ mController = spy(new AmbientVolumeUiController(mContext, mBluetoothManager,
+ mAmbientLayout, mVolumeController, mLocalDataManager));
+
+ when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
+ when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(true);
+ when(mVolumeController.isAmbientControlAvailable(mMemberDevice)).thenReturn(true);
+ when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
+ new HearingDeviceLocalDataManager.Data.Builder().build());
+
+ when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
+ Answer<Object> answer = invocationOnMock -> {
+ invocationOnMock.getArgument(0, Runnable.class).run();
+ return null;
+ };
+ when(mTestHandler.post(any(Runnable.class))).thenAnswer(answer);
+ when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(answer);
+
+ prepareDevice(/* hasMember= */ true);
+ mController.loadDevice(mCachedDevice);
+ Mockito.reset(mController);
+ Mockito.reset(mAmbientLayout);
+ }
+
+ @Test
+ public void loadDevice_deviceWithoutMember_controlNotExpandable() {
+ prepareDevice(/* hasMember= */ false);
+
+ mController.loadDevice(mCachedDevice);
+
+ verify(mAmbientLayout).setExpandable(false);
+ }
+
+ @Test
+ public void loadDevice_deviceWithMember_controlExpandable() {
+ prepareDevice(/* hasMember= */ true);
+
+ mController.loadDevice(mCachedDevice);
+
+ verify(mAmbientLayout).setExpandable(true);
+ }
+
+ @Test
+ public void loadDevice_deviceNotSupportVcp_ambientLayoutGone() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of());
+
+ mController.loadDevice(mCachedDevice);
+
+ verify(mAmbientLayout).setVisible(false);
+ }
+
+ @Test
+ public void loadDevice_ambientControlNotAvailable_ambientLayoutGone() {
+ when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(false);
+ when(mVolumeController.isAmbientControlAvailable(mMemberDevice)).thenReturn(false);
+
+ mController.loadDevice(mCachedDevice);
+
+ verify(mAmbientLayout).setVisible(false);
+ }
+
+ @Test
+ public void loadDevice_supportVcpAndAmbientControlAvailable_ambientLayoutVisible() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+ when(mVolumeController.isAmbientControlAvailable(mDevice)).thenReturn(true);
+
+ mController.loadDevice(mCachedDevice);
+
+ verify(mAmbientLayout).setVisible(true);
+ }
+
+ @Test
+ public void start_callbackRegistered() {
+ mController.start();
+
+ verify(mEventManager).registerCallback(mController);
+ verify(mLocalDataManager).start();
+ verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
+ verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
+ verify(mCachedDevice).registerCallback(any(Executor.class),
+ any(CachedBluetoothDevice.Callback.class));
+ verify(mCachedMemberDevice).registerCallback(any(Executor.class),
+ any(CachedBluetoothDevice.Callback.class));
+ }
+
+ @Test
+ public void stop_callbackUnregistered() {
+ mController.stop();
+
+ verify(mEventManager).unregisterCallback(mController);
+ verify(mLocalDataManager).stop();
+ verify(mVolumeController).unregisterCallback(mDevice);
+ verify(mVolumeController).unregisterCallback(mMemberDevice);
+ verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+ verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
+ }
+
+ @Test
+ public void onDeviceLocalDataChange_verifySetExpandedAndDataUpdated() {
+ final boolean testExpanded = true;
+ HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+ .ambient(0).groupAmbient(0).ambientControlExpanded(testExpanded).build();
+ when(mLocalDataManager.get(mDevice)).thenReturn(data);
+
+ mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ verify(mAmbientLayout).setExpanded(testExpanded);
+ verifyDeviceDataUpdated(mDevice);
+ }
+
+ @Test
+ public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
+ HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
+ .ambient(10).groupAmbient(10).ambientControlExpanded(true).build();
+ when(mLocalDataManager.get(mDevice)).thenReturn(data);
+ when(mAmbientLayout.isExpanded()).thenReturn(true);
+
+ mController.onAmbientChanged(mDevice, 10);
+ verify(mController, never()).refresh();
+
+ mController.onAmbientChanged(mDevice, 20);
+ verify(mController).refresh();
+ }
+
+ @Test
+ public void onMuteChanged_refreshWhenNotInitiateFromUi() {
+ AmbientVolumeController.RemoteAmbientState state =
+ new AmbientVolumeController.RemoteAmbientState(MUTE_NOT_MUTED, 0);
+ when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
+ when(mAmbientLayout.isExpanded()).thenReturn(false);
+
+ mController.onMuteChanged(mDevice, MUTE_NOT_MUTED);
+ verify(mController, never()).refresh();
+
+ mController.onMuteChanged(mDevice, MUTE_MUTED);
+ verify(mController).refresh();
+ }
+
+ @Test
+ public void refresh_leftAndRightDifferentGainSetting_expandControl() {
+ prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
+ prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+ when(mAmbientLayout.isExpanded()).thenReturn(false);
+
+ mController.refresh();
+
+ verify(mAmbientLayout).setExpanded(true);
+ }
+
+ @Test
+ public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
+ prepareRemoteData(mDevice, 10, MUTE_DISABLED);
+ prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+
+ mController.refresh();
+
+ verify(mAmbientLayout).setMutable(false);
+ verify(mAmbientLayout).setMuted(false);
+ }
+
+ @Test
+ public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
+ prepareRemoteData(mDevice, 10, MUTE_MUTED);
+ prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
+
+ mController.refresh();
+
+ verify(mAmbientLayout).setMutable(true);
+ verify(mAmbientLayout).setMuted(false);
+ verify(mVolumeController).setMuted(mDevice, false);
+ }
+
+ private void prepareDevice(boolean hasMember) {
+ when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+ when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+ when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
+ when(mDevice.isConnected()).thenReturn(true);
+ if (hasMember) {
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+ when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
+ when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
+ when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
+ when(mCachedMemberDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+ when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+ when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
+ when(mMemberDevice.isConnected()).thenReturn(true);
+ } else {
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of());
+ }
+ }
+
+ private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
+ when(mVolumeController.refreshAmbientState(device)).thenReturn(
+ new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
+ }
+
+ private void verifyDeviceDataUpdated(BluetoothDevice device) {
+ verify(mLocalDataManager).updateAmbient(eq(device), anyInt());
+ verify(mLocalDataManager).updateGroupAmbient(eq(device), anyInt());
+ verify(mLocalDataManager).updateAmbientControlExpanded(eq(device),
+ anyBoolean());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index fa5d54283a17..ab9f871444b4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -970,8 +970,10 @@ public class BluetoothUtilsTest {
when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
BluetoothDevice device1 = mock(BluetoothDevice.class);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
BluetoothDevice device2 = mock(BluetoothDevice.class);
+ when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device1, device2));
@@ -991,8 +993,10 @@ public class BluetoothUtilsTest {
when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
BluetoothDevice device1 = mock(BluetoothDevice.class);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
BluetoothDevice device2 = mock(BluetoothDevice.class);
+ when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device1, device2));
@@ -1012,8 +1016,10 @@ public class BluetoothUtilsTest {
when(cachedBluetoothDevice2.getGroupId()).thenReturn(2);
BluetoothDevice device1 = mock(BluetoothDevice.class);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
BluetoothDevice device2 = mock(BluetoothDevice.class);
+ when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(device2));
@@ -1035,10 +1041,13 @@ public class BluetoothUtilsTest {
when(cachedBluetoothDevice3.getGroupId()).thenReturn(3);
BluetoothDevice device1 = mock(BluetoothDevice.class);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(device1);
when(mDeviceManager.findDevice(device1)).thenReturn(mCachedBluetoothDevice);
BluetoothDevice device2 = mock(BluetoothDevice.class);
+ when(cachedBluetoothDevice2.getDevice()).thenReturn(device2);
when(mDeviceManager.findDevice(device2)).thenReturn(cachedBluetoothDevice2);
BluetoothDevice device3 = mock(BluetoothDevice.class);
+ when(cachedBluetoothDevice3.getDevice()).thenReturn(device3);
when(mDeviceManager.findDevice(device3)).thenReturn(cachedBluetoothDevice3);
when(mAssistant.getAllConnectedDevices())
@@ -1280,6 +1289,8 @@ public class BluetoothUtilsTest {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
index 6d83588e0f6e..6485636079dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
@@ -31,6 +31,8 @@ import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.utils.ThreadUtils;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -49,7 +51,10 @@ import java.util.Map;
/** Tests for {@link HearingDeviceLocalDataManager}. */
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {HearingDeviceLocalDataManagerTest.ShadowGlobal.class})
+@Config(shadows = {
+ HearingDeviceLocalDataManagerTest.ShadowGlobal.class,
+ HearingDeviceLocalDataManagerTest.ShadowThreadUtils.class,
+})
public class HearingDeviceLocalDataManagerTest {
private static final String TEST_ADDRESS = "XX:XX:XX:XX:11:22";
@@ -249,4 +254,12 @@ public class HearingDeviceLocalDataManagerTest {
return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
}
}
+
+ @Implements(value = ThreadUtils.class)
+ public static class ShadowThreadUtils {
+ @Implementation
+ protected static void postOnBackgroundThread(Runnable runnable) {
+ runnable.run();
+ }
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java
index 2b8b3b74dab9..c939c770b63d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SelectorWithWidgetPreferenceTest.java
@@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import android.app.Application;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,10 +30,8 @@ import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.widget.preference.selector.R;
-import com.android.settingslib.widget.selectorwithwidgetpreference.flags.Flags;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -45,7 +40,6 @@ import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class SelectorWithWidgetPreferenceTest {
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Application mContext;
private SelectorWithWidgetPreference mPreference;
@@ -128,26 +122,6 @@ public class SelectorWithWidgetPreferenceTest {
}
@Test
- @DisableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES)
- public void onBindViewHolder_titleMaxLinesSet_flagOff_titleMaxLinesMatchesDefault() {
- final int titleMaxLines = 5;
- AttributeSet attributeSet = Robolectric.buildAttributeSet()
- .addAttribute(R.attr.titleMaxLines, String.valueOf(titleMaxLines))
- .build();
- mPreference = new SelectorWithWidgetPreference(mContext, attributeSet);
- View view = LayoutInflater.from(mContext)
- .inflate(mPreference.getLayoutResource(), null /* root */);
- PreferenceViewHolder preferenceViewHolder =
- PreferenceViewHolder.createInstanceForTests(view);
-
- mPreference.onBindViewHolder(preferenceViewHolder);
-
- TextView title = (TextView) preferenceViewHolder.findViewById(android.R.id.title);
- assertThat(title.getMaxLines()).isEqualTo(SelectorWithWidgetPreference.DEFAULT_MAX_LINES);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES)
public void onBindViewHolder_noTitleMaxLinesSet_titleMaxLinesMatchesDefault() {
AttributeSet attributeSet = Robolectric.buildAttributeSet().build();
mPreference = new SelectorWithWidgetPreference(mContext, attributeSet);
@@ -163,7 +137,6 @@ public class SelectorWithWidgetPreferenceTest {
}
@Test
- @EnableFlags(Flags.FLAG_ALLOW_SET_TITLE_MAX_LINES)
public void onBindViewHolder_titleMaxLinesSet_titleMaxLinesUpdated() {
final int titleMaxLines = 5;
AttributeSet attributeSet = Robolectric.buildAttributeSet()
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 91ac34ac8233..de7c450d8d39 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -148,27 +148,7 @@ public final class DeviceConfigService extends Binder {
// TODO(b/364399200): use filter to skip instead?
return;
}
-
- ArrayList<String> missingFiles = new ArrayList<String>();
- for (String fileName : sAconfigTextProtoFilesOnDevice) {
- File aconfigFile = new File(fileName);
- if (!aconfigFile.exists()) {
- missingFiles.add(fileName);
- }
- }
-
- if (missingFiles.isEmpty()) {
- pw.println("\nAconfig flags:");
- for (String name : MyShellCommand.listAllAconfigFlags(iprovider)) {
- pw.println(name);
- }
- } else {
- pw.println("\nFailed to dump aconfig flags due to missing files:");
- for (String fileName : missingFiles) {
- pw.println(fileName);
- }
- }
- }
+ }
private static HashSet<String> getAconfigFlagNamesInDeviceConfig() {
HashSet<String> nameSet = new HashSet<String>();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 1c4def39eaa0..e01cb84f60ae 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -16,6 +16,19 @@
package com.android.providers.settings;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_DEVICE_SPECIFIC_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_GLOBAL;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCALE;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_LOCK_SETTINGS;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_NETWORK_POLICIES;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SECURE;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SYSTEM;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -99,22 +112,6 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final int NULL_SIZE = -1;
private static final float FONT_SCALE_DEF_VALUE = 1.0f;
- private static final String KEY_SYSTEM = "system";
- private static final String KEY_SECURE = "secure";
- private static final String KEY_GLOBAL = "global";
- private static final String KEY_LOCALE = "locale";
- private static final String KEY_LOCK_SETTINGS = "lock_settings";
- private static final String KEY_SOFTAP_CONFIG = "softap_config";
- private static final String KEY_NETWORK_POLICIES = "network_policies";
- private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
- private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config";
- private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings";
- // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a
- // fatal crash. Creating a backup with a different key will prevent Android 12 versions from
- // restoring this data.
- private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2";
- private static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data";
-
// Versioning of the state file. Increment this version
// number any time the set of state items is altered.
private static final int STATE_VERSION = 9;
@@ -257,6 +254,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) {
mBackupRestoreEventLogger = this.getBackupRestoreEventLogger();
+ mSettingsHelper.setBackupRestoreEventLogger(mBackupRestoreEventLogger);
numberOfSettingsPerKey = new HashMap<>();
areAgentMetricsEnabled = true;
}
@@ -412,9 +410,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
mSettingsHelper
.setLocaleData(
localeData,
- size,
- mBackupRestoreEventLogger,
- KEY_LOCALE);
+ size);
break;
case KEY_WIFI_CONFIG :
@@ -552,8 +548,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
if (nBytes > buffer.length) buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
mSettingsHelper
- .setLocaleData(
- buffer, nBytes, mBackupRestoreEventLogger, KEY_LOCALE);
+ .setLocaleData(buffer, nBytes);
// Restore older backups performing the necessary migrations.
if (version < FULL_BACKUP_ADDED_WIFI_NEW) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java
new file mode 100644
index 000000000000..745c2fb5409d
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupRestoreKeys.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Class to store the keys used for backup and restore.
+ */
+final class SettingsBackupRestoreKeys {
+ static final String KEY_UNKNOWN = "unknown";
+ static final String KEY_SYSTEM = "system";
+ static final String KEY_SECURE = "secure";
+ static final String KEY_GLOBAL = "global";
+ static final String KEY_LOCALE = "locale";
+ static final String KEY_LOCK_SETTINGS = "lock_settings";
+ static final String KEY_SOFTAP_CONFIG = "softap_config";
+ static final String KEY_NETWORK_POLICIES = "network_policies";
+ static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
+ static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config";
+ static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings";
+ // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a
+ // fatal crash. Creating a backup with a different key will prevent Android 12 versions from
+ // restoring this data.
+ static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2";
+ static final String KEY_WIFI_SETTINGS_BACKUP_DATA = "wifi_settings_backup_data";
+
+ /**
+ * Returns the key corresponding to the given URI.
+ *
+ * @param uri The URI of the setting's destination.
+ * @return The key corresponding to the given URI, or KEY_UNKNOWN if the URI is not recognized.
+ */
+ static String getKeyFromUri(Uri uri) {
+ if (uri.equals(Settings.Secure.CONTENT_URI)) {
+ return KEY_SECURE;
+ } else if (uri.equals(Settings.System.CONTENT_URI)) {
+ return KEY_SYSTEM;
+ } else if (uri.equals(Settings.Global.CONTENT_URI)) {
+ return KEY_GLOBAL;
+ } else {
+ return KEY_UNKNOWN;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 924c151a99a0..ab8d739feb43 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -17,6 +17,7 @@
package com.android.providers.settings;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.backup.BackupRestoreEventLogger;
@@ -84,6 +85,7 @@ public class SettingsHelper {
private Context mContext;
private AudioManager mAudioManager;
private TelephonyManager mTelephonyManager;
+ @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger;
/**
* A few settings elements are special in that a restore of those values needs to
@@ -741,11 +743,8 @@ public class SettingsHelper {
*
* @param data the comma separated BCP-47 language tags in bytes.
* @param size the size of the data in bytes.
- * @param backupRestoreEventLogger the logger to log the restore event.
- * @param dataType the data type of the setting for logging purposes.
*/
- /* package */ void setLocaleData(
- byte[] data, int size, BackupRestoreEventLogger backupRestoreEventLogger, String dataType) {
+ /* package */ void setLocaleData(byte[] data, int size) {
final Configuration conf = mContext.getResources().getConfiguration();
// Replace "_" with "-" to deal with older backups.
@@ -772,15 +771,15 @@ public class SettingsHelper {
am.updatePersistentConfigurationWithAttribution(config, mContext.getOpPackageName(),
mContext.getAttributionTag());
- if (Flags.enableMetricsSettingsBackupAgents()) {
- backupRestoreEventLogger
- .logItemsRestored(dataType, localeList.size());
+ if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+ mBackupRestoreEventLogger
+ .logItemsRestored(SettingsBackupRestoreKeys.KEY_LOCALE, localeList.size());
}
} catch (RemoteException e) {
- if (Flags.enableMetricsSettingsBackupAgents()) {
- backupRestoreEventLogger
+ if (Flags.enableMetricsSettingsBackupAgents() && mBackupRestoreEventLogger != null) {
+ mBackupRestoreEventLogger
.logItemsRestoreFailed(
- dataType,
+ SettingsBackupRestoreKeys.KEY_LOCALE,
localeList.size(),
ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA);
}
@@ -795,4 +794,13 @@ public class SettingsHelper {
AudioManager am = new AudioManager(mContext);
am.reloadAudioSettings();
}
+
+ /**
+ * Sets the backup restore event logger.
+ *
+ * @param backupRestoreEventLogger the logger to log B&R metrics.
+ */
+ void setBackupRestoreEventLogger(BackupRestoreEventLogger backupRestoreEventLogger) {
+ mBackupRestoreEventLogger = backupRestoreEventLogger;
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 55f48e3e367f..f1f03c31f718 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -120,6 +120,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.display.RefreshRateSettingsUtils;
import com.android.internal.os.BackgroundThread;
@@ -2914,6 +2915,14 @@ public class SettingsProvider extends ContentProvider {
};
}
+ @VisibleForTesting
+ void injectServices(UserManager userManager, IPackageManager packageManager,
+ SystemConfigManager sysConfigManager) {
+ mUserManager = userManager;
+ mPackageManager = packageManager;
+ mSysConfigManager = sysConfigManager;
+ }
+
private static final class Arguments {
private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS =
Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*");
@@ -3080,6 +3089,7 @@ public class SettingsProvider extends ContentProvider {
private static final String SSAID_USER_KEY = "userkey";
+ @GuardedBy("mLock")
private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
private GenerationRegistry mGenerationRegistry;
@@ -3992,6 +4002,14 @@ public class SettingsProvider extends ContentProvider {
}
}
+ @VisibleForTesting
+ void injectSettings(SettingsState settings, int type, int userId) {
+ int key = makeKey(type, userId);
+ synchronized (mLock) {
+ mSettingsStates.put(key, settings);
+ }
+ }
+
private final class MyHandler extends Handler {
private static final int MSG_NOTIFY_URI_CHANGED = 1;
private static final int MSG_NOTIFY_DATA_CHANGED = 2;
@@ -4023,12 +4041,21 @@ public class SettingsProvider extends ContentProvider {
}
}
- private final class UpgradeController {
+ @VisibleForTesting
+ final class UpgradeController {
private static final int SETTINGS_VERSION = 226;
private final int mUserId;
+ private final Injector mInjector;
+
public UpgradeController(int userId) {
+ this(/* injector= */ null, userId);
+ }
+
+ @VisibleForTesting
+ UpgradeController(Injector injector, int userId) {
+ mInjector = injector == null ? new Injector() : injector;
mUserId = userId;
}
@@ -6136,8 +6163,8 @@ public class SettingsProvider extends ContentProvider {
systemSettings.getSettingLocked(Settings.System.PEAK_REFRESH_RATE);
final Setting minRefreshRateSetting =
systemSettings.getSettingLocked(Settings.System.MIN_REFRESH_RATE);
- float highestRefreshRate = RefreshRateSettingsUtils
- .findHighestRefreshRateForDefaultDisplay(getContext());
+ float highestRefreshRate =
+ mInjector.findHighestRefreshRateForDefaultDisplay(getContext());
if (!peakRefreshRateSetting.isNull()) {
try {
@@ -6318,6 +6345,14 @@ public class SettingsProvider extends ContentProvider {
private long getBitMask(int capability) {
return 1 << (capability - 1);
}
+
+ @VisibleForTesting
+ static class Injector {
+ float findHighestRefreshRateForDefaultDisplay(Context context) {
+ return RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(
+ context);
+ }
+ }
}
/**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5cd534e62ea9..bf3afeda448e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -107,7 +107,7 @@ import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
* the same lock to grab the current state to write to disk.
* </p>
*/
-final class SettingsState {
+public class SettingsState {
private static final boolean DEBUG = false;
private static final boolean DEBUG_PERSISTENCE = false;
@@ -1838,7 +1838,7 @@ final class SettingsState {
}
}
- class Setting {
+ public class Setting {
private String name;
private String value;
private String defaultValue;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java
new file mode 100644
index 000000000000..ef537e8c7fc0
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupRestoreKeysTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SettingsBackupRestoreKeys}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SettingsBackupRestoreKeysTest {
+
+ @Test
+ public void getKeyFromUri_secureUri_returnsSecureKey() {
+ assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Secure.CONTENT_URI))
+ .isEqualTo(SettingsBackupRestoreKeys.KEY_SECURE);
+ }
+
+ @Test
+ public void getKeyFromUri_systemUri_returnsSystemKey() {
+ assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.System.CONTENT_URI))
+ .isEqualTo(SettingsBackupRestoreKeys.KEY_SYSTEM);
+ }
+
+ @Test
+ public void getKeyFromUri_globalUri_returnsGlobalKey() {
+ assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Settings.Global.CONTENT_URI))
+ .isEqualTo(SettingsBackupRestoreKeys.KEY_GLOBAL);
+ }
+
+ @Test
+ public void getKeyFromUri_unknownUri_returnsUnknownKey() {
+ assertThat(SettingsBackupRestoreKeys.getKeyFromUri(Uri.parse("content://unknown")))
+ .isEqualTo(SettingsBackupRestoreKeys.KEY_UNKNOWN);
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index 9cce43160b52..119b2870b622 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -16,7 +16,7 @@
package com.android.providers.settings;
-import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED;
+import static android.provider.Settings.Secure.CONTENT_CAPTURE_ENABLED;
import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
import static android.provider.Settings.System.RINGTONE;
@@ -67,7 +67,7 @@ public class SettingsProviderMultiUsersTest {
private static final String SPACE_SYSTEM = "system";
private static final String SPACE_SECURE = "secure";
- private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED;
+ private static final String CLONE_TO_MANAGED_PROFILE_SETTING = CONTENT_CAPTURE_ENABLED;
private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE;
private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java
new file mode 100644
index 000000000000..26ff376f828e
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/UpgradeControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import static android.provider.Settings.System.MIN_REFRESH_RATE;
+import static android.provider.Settings.System.PEAK_REFRESH_RATE;
+
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_GLOBAL;
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SECURE;
+import static com.android.providers.settings.SettingsProvider.SETTINGS_TYPE_SYSTEM;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.IPackageManager;
+import android.os.Looper;
+import android.os.SystemConfigManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class UpgradeControllerTest {
+ private static final int USER_ID = UserHandle.USER_SYSTEM;
+ private static final float HIGHEST_REFRESH_RATE = 130f;
+
+ private final Context mContext =
+ spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ private final SettingsProvider.SettingsRegistry.UpgradeController.Injector mInjector =
+ new SettingsProvider.SettingsRegistry.UpgradeController.Injector() {
+ @Override
+ float findHighestRefreshRateForDefaultDisplay(Context context) {
+ return HIGHEST_REFRESH_RATE;
+ }
+ };
+ private final SettingsProvider mSettingsProvider = new SettingsProvider() {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+ };
+ private final SettingsProvider.SettingsRegistry mSettingsRegistry =
+ mSettingsProvider.new SettingsRegistry(Looper.getMainLooper());
+ private final SettingsProvider.SettingsRegistry.UpgradeController mUpgradeController =
+ mSettingsRegistry.new UpgradeController(mInjector, USER_ID);
+
+ @Mock
+ private UserManager mUserManager;
+
+ @Mock
+ private IPackageManager mPackageManager;
+
+ @Mock
+ private SystemConfigManager mSysConfigManager;
+
+ @Mock
+ private SettingsState mSystemSettings;
+
+ @Mock
+ private SettingsState mSecureSettings;
+
+ @Mock
+ private SettingsState mGlobalSettings;
+
+ @Mock
+ private SettingsState.Setting mMockSetting;
+
+ @Mock
+ private SettingsState.Setting mPeakRefreshRateSetting;
+
+ @Mock
+ private SettingsState.Setting mMinRefreshRateSetting;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mSettingsProvider.attachInfoForTesting(mContext, /* info= */ null);
+ mSettingsProvider.injectServices(mUserManager, mPackageManager, mSysConfigManager);
+ when(mSystemSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+ when(mSecureSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+ when(mGlobalSettings.getSettingLocked(any())).thenReturn(mMockSetting);
+ when(mMockSetting.isNull()).thenReturn(true);
+ when(mMockSetting.getValue()).thenReturn("0");
+
+ when(mSystemSettings.getSettingLocked(PEAK_REFRESH_RATE))
+ .thenReturn(mPeakRefreshRateSetting);
+ when(mSystemSettings.getSettingLocked(MIN_REFRESH_RATE))
+ .thenReturn(mMinRefreshRateSetting);
+
+ mSettingsRegistry.injectSettings(mSystemSettings, SETTINGS_TYPE_SYSTEM, USER_ID);
+ mSettingsRegistry.injectSettings(mSecureSettings, SETTINGS_TYPE_SECURE, USER_ID);
+ mSettingsRegistry.injectSettings(mGlobalSettings, SETTINGS_TYPE_GLOBAL, USER_ID);
+
+ // Lowest version so that all upgrades are run
+ when(mSecureSettings.getVersionLocked()).thenReturn(118);
+ }
+
+ @Test
+ public void testUpgrade_refreshRateSettings_defaultValues() {
+ when(mPeakRefreshRateSetting.isNull()).thenReturn(true);
+ when(mMinRefreshRateSetting.isNull()).thenReturn(true);
+
+ mUpgradeController.upgradeIfNeededLocked();
+
+ // Should remain unchanged
+ verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+ /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+ /* packageName= */ any());
+ verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE),
+ /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+ /* packageName= */ any());
+ }
+
+ @Test
+ public void testUpgrade_refreshRateSettings_enabled() {
+ when(mPeakRefreshRateSetting.isNull()).thenReturn(false);
+ when(mMinRefreshRateSetting.isNull()).thenReturn(false);
+ when(mPeakRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE));
+ when(mMinRefreshRateSetting.getValue()).thenReturn(String.valueOf(HIGHEST_REFRESH_RATE));
+
+ mUpgradeController.upgradeIfNeededLocked();
+
+ // Highest refresh rate gets converted to infinity
+ verify(mSystemSettings).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+ eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(),
+ /* makeDefault= */ anyBoolean(), /* packageName= */ any());
+ verify(mSystemSettings).insertSettingLocked(eq(MIN_REFRESH_RATE),
+ eq(String.valueOf(Float.POSITIVE_INFINITY)), /* tag= */ any(),
+ /* makeDefault= */ anyBoolean(), /* packageName= */ any());
+ }
+
+ @Test
+ public void testUpgrade_refreshRateSettings_disabled() {
+ when(mPeakRefreshRateSetting.isNull()).thenReturn(false);
+ when(mMinRefreshRateSetting.isNull()).thenReturn(false);
+ when(mPeakRefreshRateSetting.getValue()).thenReturn("70f");
+ when(mMinRefreshRateSetting.getValue()).thenReturn("70f");
+
+ mUpgradeController.upgradeIfNeededLocked();
+
+ // Should remain unchanged
+ verify(mSystemSettings, never()).insertSettingLocked(eq(PEAK_REFRESH_RATE),
+ /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+ /* packageName= */ any());
+ verify(mSystemSettings, never()).insertSettingLocked(eq(MIN_REFRESH_RATE),
+ /* value= */ any(), /* tag= */ any(), /* makeDefault= */ anyBoolean(),
+ /* packageName= */ any());
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fb4293a9b5ea..46bd88fcdc93 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -994,6 +994,9 @@
<uses-permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
+ <!-- Permission required for CTS test - CtsContentProviderMultiUserTest -->
+ <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3d250fd82473..0600fb3abd6f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -85,6 +85,7 @@ filegroup {
filegroup {
name: "SystemUI-tests-broken-robofiles-run",
srcs: [
+ "tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java",
"tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
"tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt",
@@ -418,6 +419,9 @@ android_library {
"androidx.slice_slice-view",
],
manifest: "AndroidManifest-res.xml",
+ flags_packages: [
+ "com_android_systemui_flags",
+ ],
}
android_library {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 1b1c91de1e56..5ff2d1b07347 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -496,6 +496,13 @@ flag {
}
flag {
+ name: "status_bar_popup_chips"
+ namespace: "systemui"
+ description: "Show rich ongoing processes as chips in the status bar"
+ bug: "372964148"
+}
+
+flag {
name: "promote_notifications_automatically"
namespace: "systemui"
description: "Flag to automatically turn certain notifications into promoted notifications so "
@@ -1229,6 +1236,21 @@ flag {
}
flag {
+ name: "glanceable_hub_v2_resources"
+ namespace: "systemui"
+ description: "Read only flag for rolling out glanceable hub v2 resource values"
+ bug: "375689917"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "glanceable_hub_back_action"
+ namespace: "systemui"
+ description: "Support back action from glanceable hub"
+ bug: "382771533"
+}
+
+flag {
name: "dream_overlay_updated_font"
namespace: "systemui"
description: "Flag to enable updated font settings for dream overlay"
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
index ca2b9578f2be..7d27a562f536 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -195,7 +195,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
// Create the origin leash and add to the transition root leash.
mOriginLeash =
new SurfaceControl.Builder().setName("OriginTransition-origin-leash").build();
- mStartTransaction
+
+ // Create temporary transaction to build
+ final SurfaceControl.Transaction tmpTransaction = new SurfaceControl.Transaction();
+ tmpTransaction
.reparent(mOriginLeash, rootLeash)
.show(mOriginLeash)
.setCornerRadius(mOriginLeash, windowRadius)
@@ -208,14 +211,14 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
int mode = change.getMode();
SurfaceControl leash = change.getLeash();
// Reparent leash to the transition root.
- mStartTransaction.reparent(leash, rootLeash);
+ tmpTransaction.reparent(leash, rootLeash);
if (TransitionUtil.isOpeningMode(mode)) {
openingSurfaces.add(change.getLeash());
// For opening surfaces, ending bounds are base bound. Apply corner radius if
// it's full screen.
Rect bounds = change.getEndAbsBounds();
if (displayBounds.equals(bounds)) {
- mStartTransaction
+ tmpTransaction
.setCornerRadius(leash, windowRadius)
.setWindowCrop(leash, bounds.width(), bounds.height());
}
@@ -226,28 +229,53 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
// it's full screen.
Rect bounds = change.getStartAbsBounds();
if (displayBounds.equals(bounds)) {
- mStartTransaction
+ tmpTransaction
.setCornerRadius(leash, windowRadius)
.setWindowCrop(leash, bounds.width(), bounds.height());
}
}
}
+ if (openingSurfaces.isEmpty() && closingSurfaces.isEmpty()) {
+ logD("prepareUIs: no opening/closing surfaces available, nothing to prepare.");
+ return false;
+ }
+
// Set relative order:
// ---- App1 ----
// ---- origin ----
// ---- App2 ----
+
if (mIsEntry) {
- mStartTransaction
- .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1)
- .setRelativeLayer(
- openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1);
+ if (!closingSurfaces.isEmpty()) {
+ tmpTransaction
+ .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1);
+ } else {
+ logW("Missing closing surface is entry transition");
+ }
+ if (!openingSurfaces.isEmpty()) {
+ tmpTransaction
+ .setRelativeLayer(
+ openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1);
+ } else {
+ logW("Missing opening surface is entry transition");
+ }
+
} else {
- mStartTransaction
- .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1)
- .setRelativeLayer(
- closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1);
+ if (!openingSurfaces.isEmpty()) {
+ tmpTransaction
+ .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1);
+ } else {
+ logW("Missing opening surface is exit transition");
+ }
+ if (!closingSurfaces.isEmpty()) {
+ tmpTransaction.setRelativeLayer(
+ closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1);
+ } else {
+ logW("Missing closing surface is exit transition");
+ }
}
+ mStartTransaction.merge(tmpTransaction);
// Attach origin UIComponent to origin leash.
mOriginTransaction = mOrigin.newTransaction();
@@ -300,6 +328,7 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
}
private void cancel() {
+ logD("cancel()");
if (mAnimator != null) {
mAnimator.cancel();
}
@@ -311,6 +340,10 @@ public class OriginRemoteTransition extends IRemoteTransition.Stub {
}
}
+ private static void logW(String msg) {
+ Log.w(TAG, msg);
+ }
+
private static void logE(String msg) {
Log.e(TAG, msg);
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index a17a1d46554f..0a0003ee9a8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,17 +19,20 @@ package com.android.systemui.communal.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
@@ -39,8 +42,11 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlin.math.min
+import kotlin.math.roundToInt
/** Renders the content of the glanceable hub. */
class CommunalContent
@@ -48,6 +54,7 @@ class CommunalContent
constructor(
private val viewModel: CommunalViewModel,
private val interactionHandler: SmartspaceInteractionHandler,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val dialogFactory: SystemUIDialogFactory,
private val lockSection: LockSection,
private val bottomAreaSection: BottomAreaSection,
@@ -77,11 +84,20 @@ constructor(
sceneScope = this@Content,
)
}
- with(lockSection) {
- LockIcon(
- overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_lock),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.element(Communal.Elements.LockIcon),
)
+ } else {
+ with(lockSection) {
+ LockIcon(
+ overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier.element(Communal.Elements.LockIcon),
+ )
+ }
}
with(bottomAreaSection) {
IndicationArea(
@@ -98,14 +114,42 @@ constructor(
val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0)
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconPlaceable =
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ val lockIconSizeInt = lockIconSize.roundToPx()
+ lockIconMeasurable.measure(
+ Constraints.fixed(width = lockIconSizeInt, height = lockIconSizeInt)
+ )
+ } else {
+ 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],
- )
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ val lockIconDistanceFromBottom =
+ min(
+ (constraints.maxHeight * lockIconPercentDistanceFromBottom)
+ .roundToInt(),
+ lockIconMinDistanceFromBottom.roundToPx(),
+ )
+ val x = constraints.maxWidth / 2 - lockIconPlaceable.width / 2
+ val y =
+ constraints.maxHeight -
+ lockIconDistanceFromBottom -
+ lockIconPlaceable.height
+ IntRect(
+ left = x,
+ top = y,
+ right = x + lockIconPlaceable.width,
+ bottom = y + lockIconPlaceable.height,
+ )
+ } else {
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+ }
val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
@@ -129,12 +173,17 @@ constructor(
val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height
bottomAreaPlaceable.place(x = 0, y = bottomAreaTop)
+
+ val screensaverButtonPaddingInt = screensaverButtonPadding.roundToPx()
screensaverButtonPlaceable?.place(
x =
constraints.maxWidth -
screensaverButtonSizeInt -
- Dimensions.ItemSpacing.roundToPx(),
- y = lockIconBounds.top,
+ screensaverButtonPaddingInt,
+ y =
+ constraints.maxHeight -
+ screensaverButtonSizeInt -
+ screensaverButtonPaddingInt,
)
}
}
@@ -142,6 +191,12 @@ constructor(
}
companion object {
- val screensaverButtonSize: Dp = 64.dp
+ private val screensaverButtonSize: Dp = 64.dp
+ private val screensaverButtonPadding: Dp = 24.dp
+ // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
+ // position are sorted.
+ private val lockIconSize: Dp = 54.dp
+ private val lockIconPercentDistanceFromBottom = 0.1f
+ private val lockIconMinDistanceFromBottom = 70.dp
}
}
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 5dbedc7045e4..bf3360f0ea14 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
@@ -931,7 +931,9 @@ private fun BoxScope.CommunalHubLazyGrid(
Modifier.requiredSize(dpSize)
.thenIf(!isItemDragging) {
Modifier.animateItem(
- placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
+ placementSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ // See b/376495198 - not supported with AndroidView
+ fadeOutSpec = null,
)
}
.thenIf(isItemDragging) { Modifier.zIndex(1f) },
@@ -980,11 +982,14 @@ private fun BoxScope.CommunalHubLazyGrid(
size = size,
selected = false,
modifier =
- Modifier.requiredSize(dpSize).animateItem().thenIf(
- communalResponsiveGrid()
- ) {
- Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f }
- },
+ Modifier.requiredSize(dpSize)
+ .animateItem(
+ // See b/376495198 - not supported with AndroidView
+ fadeOutSpec = null
+ )
+ .thenIf(communalResponsiveGrid()) {
+ Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f }
+ },
index = index,
contentListState = contentListState,
interactionHandler = interactionHandler,
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 5c7ca97474b7..0344ab8e0196 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
@@ -38,7 +38,6 @@ import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -90,14 +89,10 @@ constructor(
) {
init {
- if (!MigrateClocksToBlueprint.isEnabled) {
- throw IllegalStateException("this requires MigrateClocksToBlueprint.isEnabled")
- }
// This scene container section moves the NSSL to the SharedNotificationContainer.
// This also requires that SharedNotificationContainer gets moved to the
// SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
- // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
- // container by the NotificationStackScrollLayoutSection.
+ // NSSL is moved into this container by the NotificationStackScrollLayoutSection.
// Ensure stackScrollLayout is a child of sharedNotificationContainer.
if (stackScrollLayout.parent != sharedNotificationContainer) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index db33e7c628d7..79cf24b9c547 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -35,9 +35,8 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -61,7 +60,7 @@ constructor(
private val clockInteractor: KeyguardClockInteractor,
) {
@Composable
- fun SceneScope.DefaultClockLayout(
+ fun ContentScope.DefaultClockLayout(
smartSpacePaddingTop: (Resources) -> Int,
isShadeLayoutWide: Boolean,
modifier: Modifier = Modifier,
@@ -95,7 +94,7 @@ constructor(
}
Column(modifier) {
- SceneTransitionLayout(state) {
+ NestedSceneTransitionLayout(state, Modifier) {
scene(splitShadeLargeClockScene) {
LargeClockWithSmartSpace(
smartSpacePaddingTop = smartSpacePaddingTop,
@@ -134,7 +133,7 @@ constructor(
}
@Composable
- private fun SceneScope.SmallClockWithSmartSpace(
+ private fun ContentScope.SmallClockWithSmartSpace(
smartSpacePaddingTop: (Resources) -> Int,
modifier: Modifier = Modifier,
) {
@@ -159,7 +158,7 @@ constructor(
}
@Composable
- private fun SceneScope.LargeClockWithSmartSpace(
+ private fun ContentScope.LargeClockWithSmartSpace(
smartSpacePaddingTop: (Resources) -> Int,
shouldOffSetClockToOneHalf: Boolean = false,
) {
@@ -200,7 +199,7 @@ constructor(
}
@Composable
- private fun SceneScope.WeatherLargeClockWithSmartSpace(
+ private fun ContentScope.WeatherLargeClockWithSmartSpace(
smartSpacePaddingTop: (Resources) -> Int,
modifier: Modifier = Modifier,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 2af5ffaee7ed..5790c4af0d77 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import com.android.compose.animation.scene.ContentScope
@@ -84,7 +85,11 @@ constructor(
viewModel.notificationsPlaceholderViewModelFactory.create()
}
- OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
+ OverlayShade(
+ panelAlignment = Alignment.TopStart,
+ modifier = modifier,
+ onScrimClicked = viewModel::onScrimClicked,
+ ) {
Column {
if (viewModel.showHeader) {
val burnIn = rememberBurnIn(clockInteractor)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index b1a19456ab7d..f6c5f588aa95 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -99,7 +99,11 @@ constructor(
val viewModel =
rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
- OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) {
+ OverlayShade(
+ panelAlignment = Alignment.TopEnd,
+ modifier = modifier,
+ onScrimClicked = viewModel::onScrimClicked,
+ ) {
Column {
ExpandedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 55fafd5cfeca..8907aec7fd48 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -5,7 +5,6 @@ import androidx.compose.foundation.gestures.Orientation
import com.android.compose.animation.scene.ProgressConverter
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transitions
-import com.android.systemui.bouncer.ui.composable.Bouncer
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
@@ -110,17 +109,13 @@ val SceneContainerTransitions = transitions {
// Overlay transitions
- // TODO(b/376659778): Remove this transition once nested STLs are supported.
- from(Scenes.Gone, to = Overlays.NotificationsShade) {
- toNotificationsShadeTransition(translateClock = true)
- }
to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) {
notificationsShadeToQuickSettingsShadeTransition()
}
from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
- toNotificationsShadeTransition(translateClock = true, durationScale = 0.9)
+ toNotificationsShadeTransition(durationScale = 0.9)
}
from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
toQuickSettingsShadeTransition(durationScale = 0.9)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 6bdb36331709..3d62151baf2f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -29,10 +29,7 @@ import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
-fun TransitionBuilder.toNotificationsShadeTransition(
- translateClock: Boolean = false,
- durationScale: Double = 1.0,
-) {
+fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
swipeSpec =
spring(
@@ -45,11 +42,6 @@ fun TransitionBuilder.toNotificationsShadeTransition(
elevateInContent = Overlays.NotificationsShade,
)
scaleSize(OverlayShade.Elements.Panel, height = 0f)
- // TODO(b/376659778): This is a temporary hack to have a shared element transition with the
- // lockscreen clock. Remove once nested STLs are supported.
- if (!translateClock) {
- translate(ClockElementKeys.smallClockElementKey)
- }
// Avoid translating the status bar with the shade panel.
translate(NotificationsShade.Elements.StatusBar)
// Slide in the shade panel from the top edge.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 8a5c96da5ac6..cfbe6671db02 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -20,6 +20,9 @@ package com.android.systemui.shade.ui.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
@@ -41,30 +44,51 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
-import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.effect.rememberOffsetOverscrollEffect
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.res.R
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
-fun SceneScope.OverlayShade(
+fun ContentScope.OverlayShade(
+ panelAlignment: Alignment,
onScrimClicked: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
- Box(modifier) {
+ // TODO(b/384653288) This should be removed when b/378470603 is done.
+ val idleEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+ Box(
+ modifier
+ .overscroll(idleEffect)
+ .nestedScroll(
+ remember {
+ object : NestedScrollConnection {
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ return available
+ }
+ }
+ }
+ )
+ .scrollable(rememberScrollableState { 0f }, Orientation.Vertical, idleEffect)
+ ) {
Scrim(onClicked = onScrimClicked)
- Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = Alignment.TopEnd) {
+ Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) {
Panel(
modifier =
Modifier.element(OverlayShade.Elements.Panel)
@@ -77,7 +101,7 @@ fun SceneScope.OverlayShade(
}
@Composable
-private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) {
+private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifier) {
Spacer(
modifier =
modifier
@@ -89,7 +113,7 @@ private fun SceneScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modifie
}
@Composable
-private fun SceneScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) {
Spacer(
modifier =
@@ -180,7 +204,6 @@ object OverlayShade {
object Dimensions {
val PanelCornerRadius = 46.dp
- val OverscrollLimit = 32.dp
}
object Shapes {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index fa5f72bc0997..1480db9de701 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -37,12 +37,14 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.CustomAccessibilityAction
@@ -66,6 +68,10 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState
+import kotlin.math.round
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
@Composable
fun VolumeSlider(
@@ -196,9 +202,17 @@ private fun LegacyVolumeSlider(
)
}
}
-
- // Perform haptics due to UI composition
- hapticsViewModel?.onValueChange(value)
+ var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) }
+ LaunchedEffect(value) {
+ snapshotFlow { value }
+ .map { round(it) }
+ .filter { it != lastDiscreteStep }
+ .distinctUntilChanged()
+ .collect { discreteStep ->
+ lastDiscreteStep = discreteStep
+ hapticsViewModel?.onValueChange(discreteStep)
+ }
+ }
PlatformSlider(
modifier =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
index 599a152a23bd..167928b38e90 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SharedElement.kt
@@ -30,7 +30,8 @@ internal fun Element.shouldBeRenderedBy(content: ContentKey): Boolean {
// the transition is running. If the [renderAuthority.size] is 1 it means that that this element
// is currently composed only in one nesting level, which means that the render authority
// is determined by "classic" shared element code.
- return renderAuthority.size == 1 || renderAuthority.first() == content
+ return renderAuthority.size > 0 &&
+ (renderAuthority.size == 1 || renderAuthority.first() == content)
}
/**
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 801a2d6170cc..b76656d78cc4 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
@@ -71,7 +71,6 @@ constructor(
}
var hasCustomPositionUpdatedAnimation: Boolean = false
- var migratedClocks: Boolean = false
private val time = Calendar.getInstance()
@@ -228,11 +227,7 @@ constructor(
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure")
- if (
- migratedClocks &&
- !isSingleLineInternal &&
- MeasureSpec.getMode(heightMeasureSpec) == EXACTLY
- ) {
+ if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
// Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize
val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)
super.setTextSize(COMPLEX_UNIT_PX, size)
@@ -248,7 +243,7 @@ constructor(
}
}
- if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+ if (hasCustomPositionUpdatedAnimation) {
// Expand width to avoid clock being clipped during stepping animation
val targetWidth = measuredWidth + MeasureSpec.getSize(widthMeasureSpec) / 2
@@ -582,12 +577,10 @@ constructor(
}
override fun onRtlPropertiesChanged(layoutDirection: Int) {
- if (migratedClocks) {
- if (layoutDirection == LAYOUT_DIRECTION_RTL) {
- textAlignment = TEXT_ALIGNMENT_TEXT_END
- } else {
- textAlignment = TEXT_ALIGNMENT_TEXT_START
- }
+ if (layoutDirection == LAYOUT_DIRECTION_RTL) {
+ textAlignment = TEXT_ALIGNMENT_TEXT_END
+ } else {
+ textAlignment = TEXT_ALIGNMENT_TEXT_START
}
super.onRtlPropertiesChanged(layoutDirection)
}
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 ad9eba841c86..74d595ce65e6 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
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
-import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
@@ -55,7 +54,6 @@ class DefaultClockController(
private val layoutInflater: LayoutInflater,
private val resources: Resources,
private val settings: ClockSettings?,
- private val migratedClocks: Boolean = false,
messageBuffers: ClockMessageBuffers? = null,
) : ClockController {
override val smallClock: DefaultClockFaceController
@@ -67,7 +65,6 @@ class DefaultClockController(
private val burmeseLineSpacing =
resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
- protected var onSecondaryDisplay: Boolean = false
override val events: DefaultClockEvents
override val config: ClockConfig by lazy {
@@ -175,10 +172,7 @@ class DefaultClockController(
recomputePadding(targetRegion)
}
- override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
- this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
- recomputePadding(null)
- }
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
}
open fun recomputePadding(targetRegion: Rect?) {}
@@ -197,32 +191,11 @@ class DefaultClockController(
override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
init {
- view.migratedClocks = migratedClocks
view.hasCustomPositionUpdatedAnimation = true
animations = LargeClockAnimations(view, 0f, 0f)
}
- override fun recomputePadding(targetRegion: Rect?) {
- if (migratedClocks) {
- return
- }
- // We center the view within the targetRegion instead of within the parent
- // view by computing the difference and adding that to the padding.
- val lp = view.getLayoutParams() as FrameLayout.LayoutParams
- lp.topMargin =
- if (onSecondaryDisplay) {
- // On the secondary display we don't want any additional top/bottom margin.
- 0
- } else {
- val parent = view.parent
- val yDiff =
- if (targetRegion != null && parent is View && parent.isLaidOut())
- targetRegion.centerY() - parent.height / 2f
- else 0f
- (-0.5f * view.bottom + yDiff).toInt()
- }
- view.setLayoutParams(lp)
- }
+ override fun recomputePadding(targetRegion: Rect?) {}
/** See documentation at [AnimatableClockView.offsetGlyphsForStepClockAnimation]. */
fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) {
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 e8987257bb47..c73e1c33f88a 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
@@ -47,7 +47,6 @@ class DefaultClockProvider(
val ctx: Context,
val layoutInflater: LayoutInflater,
val resources: Resources,
- private val migratedClocks: Boolean = false,
private val isClockReactiveVariantsEnabled: Boolean = false,
) : ClockProvider {
private var messageBuffers: ClockMessageBuffers? = null
@@ -83,14 +82,7 @@ class DefaultClockProvider(
FLEX_DESIGN,
)
} else {
- DefaultClockController(
- ctx,
- layoutInflater,
- resources,
- settings,
- migratedClocks,
- messageBuffers,
- )
+ DefaultClockController(ctx, layoutInflater, resources, settings, messageBuffers)
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 21d41ae744a7..4a47f1bc12bf 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -140,8 +140,8 @@ class FlexClockFaceController(
}
/**
- * targetRegion passed to all customized clock applies counter translationY of
- * KeyguardStatusView and keyguard_large_clock_top_margin from default clock
+ * targetRegion passed to all customized clock applies counter translationY of Keyguard and
+ * keyguard_large_clock_top_margin from default clock
*/
override fun onTargetRegionChanged(targetRegion: Rect?) {
// When a clock needs to be aligned with screen, like weather clock
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index fee82dfcf2e3..813038ee81ec 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -43,12 +43,6 @@ present in the source tree, although it will likely be removed in a later patch.
SystemUI event dispatchers to the clock controllers. It maintains a set of event listeners, but
otherwise attempts to do as little work as possible. It does maintain some state where necessary.
-[KeyguardClockSwitchController](../src/com/android/keyguard/KeyguardClockSwitchController.java) is
-the primary controller for the [KeyguardClockSwitch](../src/com/android/keyguard/KeyguardClockSwitch.java),
-which serves as the view parent within SystemUI. Together they ensure the correct clock (either
-large or small) is shown, handle animation between clock sizes, and control some sizing/layout
-parameters for the clocks.
-
### Creating a custom clock
In order to create a custom clock, a partner must:
- Write an implementation of ClockProviderPlugin and the subinterfaces relevant to your use-case.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
index 85bdf9264467..cea1e9600741 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.kt
@@ -163,6 +163,16 @@ class KeyguardDisplayManagerTest : SysuiTestCase() {
}
@Test
+ fun testShow_rearDisplayOuterDefaultActive_occluded() {
+ displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
+
+ whenever(deviceStateHelper.isRearDisplayOuterDefaultActive(secondaryDisplay))
+ .thenReturn(true)
+ whenever(keyguardStateController.isOccluded).thenReturn(true)
+ verify(presentationFactory, never()).create(eq(secondaryDisplay))
+ }
+
+ @Test
fun testShow_presentationCreated() {
displayTracker.allDisplays = arrayOf(defaultDisplay, secondaryDisplay)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java
new file mode 100644
index 000000000000..455329f54864
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayoutTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hearingaid;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.ROTATION_COLLAPSED;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.ROTATION_EXPANDED;
+import static com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout.SIDE_UNIFIED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Map;
+
+/** Tests for {@link AmbientVolumeLayout}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AmbientVolumeLayoutTest extends SysuiTestCase {
+
+ private static final int TEST_LEFT_VOLUME_LEVEL = 1;
+ private static final int TEST_RIGHT_VOLUME_LEVEL = 2;
+ private static final int TEST_UNIFIED_VOLUME_LEVEL = 3;
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock
+ private AmbientVolumeUi.AmbientVolumeUiListener mListener;
+
+ private AmbientVolumeLayout mLayout;
+ private ImageView mExpandIcon;
+ private ImageView mVolumeIcon;
+ private final Map<Integer, BluetoothDevice> mSideToDeviceMap = new ArrayMap<>();
+
+ @Before
+ public void setUp() {
+ mLayout = new AmbientVolumeLayout(mContext);
+ mLayout.setListener(mListener);
+ mLayout.setExpandable(true);
+ mLayout.setMutable(true);
+
+ prepareDevices();
+ mLayout.setupSliders(mSideToDeviceMap);
+ mLayout.getSliders().forEach((side, slider) -> {
+ slider.setMin(0);
+ slider.setMax(4);
+ if (side == SIDE_LEFT) {
+ slider.setValue(TEST_LEFT_VOLUME_LEVEL);
+ } else if (side == SIDE_RIGHT) {
+ slider.setValue(TEST_RIGHT_VOLUME_LEVEL);
+ } else if (side == SIDE_UNIFIED) {
+ slider.setValue(TEST_UNIFIED_VOLUME_LEVEL);
+ }
+ });
+
+ mExpandIcon = mLayout.getExpandIcon();
+ mVolumeIcon = mLayout.getVolumeIcon();
+ }
+
+ @Test
+ public void setExpandable_expandable_expandIconVisible() {
+ mLayout.setExpandable(true);
+
+ assertThat(mExpandIcon.getVisibility()).isEqualTo(VISIBLE);
+ }
+
+ @Test
+ public void setExpandable_notExpandable_expandIconGone() {
+ mLayout.setExpandable(false);
+
+ assertThat(mExpandIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setExpanded_expanded_assertControlUiCorrect() {
+ mLayout.setExpanded(true);
+
+ assertControlUiCorrect();
+ }
+
+ @Test
+ public void setExpanded_notExpanded_assertControlUiCorrect() {
+ mLayout.setExpanded(false);
+
+ assertControlUiCorrect();
+ }
+
+ @Test
+ public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
+ mLayout.setMutable(true);
+ mLayout.setMuted(false);
+
+ mVolumeIcon.callOnClick();
+
+ assertThat(mLayout.isMuted()).isTrue();
+ }
+
+ @Test
+ public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
+ mLayout.setMutable(false);
+ mLayout.setMuted(false);
+
+ mVolumeIcon.callOnClick();
+
+ assertThat(mLayout.isMuted()).isFalse();
+ }
+
+ @Test
+ public void updateLayout_mute_volumeIconIsCorrect() {
+ mLayout.setMuted(true);
+ mLayout.updateLayout();
+
+ assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
+ }
+
+ @Test
+ public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
+ mLayout.setMuted(false);
+ mLayout.setExpanded(true);
+ mLayout.updateLayout();
+
+ int expectedLevel = calculateVolumeLevel(TEST_LEFT_VOLUME_LEVEL, TEST_RIGHT_VOLUME_LEVEL);
+ assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
+ mLayout.setMuted(false);
+ mLayout.setExpanded(false);
+ mLayout.updateLayout();
+
+ int expectedLevel = calculateVolumeLevel(TEST_UNIFIED_VOLUME_LEVEL,
+ TEST_UNIFIED_VOLUME_LEVEL);
+ assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void setSliderEnabled_expandedAndLeftIsDisabled_volumeIconIsCorrect() {
+ mLayout.setExpanded(true);
+ mLayout.setSliderEnabled(SIDE_LEFT, false);
+
+ int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
+ assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void setSliderValue_expandedAndLeftValueChanged_volumeIconIsCorrect() {
+ mLayout.setExpanded(true);
+ mLayout.setSliderValue(SIDE_LEFT, 4);
+
+ int expectedLevel = calculateVolumeLevel(4, TEST_RIGHT_VOLUME_LEVEL);
+ assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
+ }
+
+ private int calculateVolumeLevel(int left, int right) {
+ return left * 5 + right;
+ }
+
+ private void assertControlUiCorrect() {
+ final boolean expanded = mLayout.isExpanded();
+ final Map<Integer, AmbientVolumeSlider> sliders = mLayout.getSliders();
+ if (expanded) {
+ assertThat(sliders.get(SIDE_UNIFIED).getVisibility()).isEqualTo(GONE);
+ assertThat(sliders.get(SIDE_LEFT).getVisibility()).isEqualTo(VISIBLE);
+ assertThat(sliders.get(SIDE_RIGHT).getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mExpandIcon.getRotation()).isEqualTo(ROTATION_EXPANDED);
+ } else {
+ assertThat(sliders.get(SIDE_UNIFIED).getVisibility()).isEqualTo(VISIBLE);
+ assertThat(sliders.get(SIDE_LEFT).getVisibility()).isEqualTo(GONE);
+ assertThat(sliders.get(SIDE_RIGHT).getVisibility()).isEqualTo(GONE);
+ assertThat(mExpandIcon.getRotation()).isEqualTo(ROTATION_COLLAPSED);
+ }
+ }
+
+ private void prepareDevices() {
+ mSideToDeviceMap.put(SIDE_LEFT, mock(BluetoothDevice.class));
+ mSideToDeviceMap.put(SIDE_RIGHT, mock(BluetoothDevice.class));
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java
new file mode 100644
index 000000000000..78dfda88a526
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSliderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for {@link AmbientVolumeLayout}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AmbientVolumeSliderTest extends SysuiTestCase {
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
+
+ private AmbientVolumeSlider mSlider;
+
+ @Before
+ public void setUp() {
+ mSlider = new AmbientVolumeSlider(mContext);
+ }
+
+ @Test
+ public void setTitle_titleCorrect() {
+ final String testTitle = "test";
+ mSlider.setTitle(testTitle);
+
+ assertThat(mSlider.getTitle()).isEqualTo(testTitle);
+ }
+
+ @Test
+ public void getVolumeLevel_valueMin_volumeLevelIsZero() {
+ prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 0);
+
+ // The volume level is divided into 5 levels:
+ // Level 0 corresponds to the minimum volume value. The range between the minimum and
+ // maximum volume is divided into 4 equal intervals, represented by levels 1 to 4.
+ assertThat(mSlider.getVolumeLevel()).isEqualTo(0);
+ }
+
+ @Test
+ public void getVolumeLevel_valueMax_volumeLevelIsFour() {
+ prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 100);
+
+ assertThat(mSlider.getVolumeLevel()).isEqualTo(4);
+ }
+
+ @Test
+ public void getVolumeLevel_volumeLevelIsCorrect() {
+ prepareSlider(/* min= */ 0, /* max= */ 100, /* value= */ 73);
+
+ assertThat(mSlider.getVolumeLevel()).isEqualTo(3);
+ }
+
+ private void prepareSlider(float min, float max, float value) {
+ mSlider.setMin(min);
+ mSlider.setMax(max);
+ mSlider.setValue(value);
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index ad12c61ab5d1..43d0d69c428f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -16,8 +16,11 @@
package com.android.systemui.accessibility.hearingaid;
+import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT;
import static com.google.common.truth.Truth.assertThat;
@@ -31,6 +34,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.AudioInputControl;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapPresetInfo;
import android.bluetooth.BluetoothProfile;
@@ -61,6 +65,7 @@ import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -90,6 +95,7 @@ import java.util.List;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
+
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -120,6 +126,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
@Mock
private HapClientProfile mHapClientProfile;
@Mock
+ private VolumeControlProfile mVolumeControlProfile;
+ @Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private BluetoothEventManager mBluetoothEventManager;
@@ -151,21 +159,25 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
+ when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mDevice.getBondState()).thenReturn(BOND_BONDED);
when(mDevice.isConnected()).thenReturn(true);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
- when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.getProfiles()).thenReturn(
+ List.of(mHapClientProfile, mVolumeControlProfile));
when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, ""));
+ when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
+ when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
mContext.setMockPackageManager(mPackageManager);
@@ -292,6 +304,46 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+ public void showDialog_deviceNotSupportVcp_ambientLayoutGone() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of());
+
+ setUpDeviceDialogWithoutPairNewDeviceButton();
+ mDialog.show();
+
+ ViewGroup ambientLayout = getAmbientLayout(mDialog);
+ assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+ public void showDialog_ambientControlNotAvailable_ambientLayoutGone() {
+ when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(List.of());
+
+ setUpDeviceDialogWithoutPairNewDeviceButton();
+ mDialog.show();
+
+ ViewGroup ambientLayout = getAmbientLayout(mDialog);
+ assertThat(ambientLayout.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ @EnableFlags(com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_AMBIENT_VOLUME_CONTROL)
+ public void showDialog_supportVcpAndAmbientControlAvailable_ambientLayoutVisible() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
+ AudioInputControl audioInputControl = prepareAudioInputControl();
+ when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(
+ List.of(audioInputControl));
+ when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(STATE_CONNECTED);
+
+ setUpDeviceDialogWithoutPairNewDeviceButton();
+ mDialog.show();
+
+ ViewGroup ambientLayout = getAmbientLayout(mDialog);
+ assertThat(ambientLayout.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void onActiveDeviceChanged_presetExist_presetSelected() {
setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
@@ -368,6 +420,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
return dialog.requireViewById(R.id.preset_layout);
}
+ private ViewGroup getAmbientLayout(SystemUIDialog dialog) {
+ return dialog.requireViewById(R.id.ambient_layout);
+ }
+
private int countChildWithoutSpace(ViewGroup viewGroup) {
int spaceCount = 0;
@@ -388,6 +444,16 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility);
}
+ private AudioInputControl prepareAudioInputControl() {
+ AudioInputControl audioInputControl = mock(AudioInputControl.class);
+ when(audioInputControl.getAudioInputType()).thenReturn(
+ AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT);
+ when(audioInputControl.getGainMode()).thenReturn(AudioInputControl.GAIN_MODE_MANUAL);
+ when(audioInputControl.getAudioInputStatus()).thenReturn(
+ AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE);
+ return audioInputControl;
+ }
+
@After
public void reset() {
if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index fa5af510fec1..77e386963129 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -127,6 +127,7 @@ class ShadeTouchHandlerTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
@DisableFlags(
Flags.FLAG_COMMUNAL_HUB,
+ Flags.FLAG_GLANCEABLE_HUB_V2,
Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX,
Flags.FLAG_SCENE_CONTAINER,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 41cc6ee182cf..271cd3a4f202 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.back.domain.interactor
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -31,6 +32,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -93,6 +95,7 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
+ @Mock private lateinit var communalBackActionInteractor: CommunalBackActionInteractor
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -117,6 +120,7 @@ class BackActionInteractorTest : SysuiTestCase() {
windowRootViewVisibilityInteractor,
shadeBackActionInteractor,
qsController,
+ communalBackActionInteractor,
)
}
@@ -306,6 +310,19 @@ class BackActionInteractorTest : SysuiTestCase() {
verify(shadeBackActionInteractor).onBackProgressed(0.4f)
}
+ @Test
+ @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_BACK_ACTION)
+ fun onBackAction_communalCanBeDismissed_communalBackActionInteractorCalled() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ powerInteractor.setAwakeForTest()
+ val callback = getBackInvokedCallback()
+ whenever(communalBackActionInteractor.canBeDismissed()).thenReturn(true)
+ callback.onBackInvoked()
+
+ verify(communalBackActionInteractor).onBackPressed()
+ }
+
private fun getBackInvokedCallback(): OnBackInvokedCallback {
testScope.runCurrent()
val captor = argumentCaptor<OnBackInvokedCallback>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 08f139c6a3af..9c9d5adcfcc9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -51,7 +51,7 @@ class BiometricPromptRequestTest : SysuiTestCase() {
title = title,
subtitle = subtitle,
description = description,
- contentView = contentView
+ contentView = contentView,
),
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
@@ -101,9 +101,7 @@ class BiometricPromptRequestTest : SysuiTestCase() {
val fpPros = fingerprintSensorPropertiesInternal().first()
val request =
BiometricPromptRequest.Biometric(
- promptInfo(
- logoBitmap = logoBitmap,
- ),
+ promptInfo(logoBitmap = logoBitmap),
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
BiometricModalities(fingerprintProperties = fpPros),
@@ -162,7 +160,7 @@ class BiometricPromptRequestTest : SysuiTestCase() {
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
stealth,
- )
+ ),
)
for (request in toCheck) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
index 370adee44a42..03bf79bc7a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalMetricsStartableTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.communal
import android.app.StatsManager
import android.app.StatsManager.StatsPullAtomCallback
import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.util.StatsEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -32,6 +33,7 @@ import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shared.system.SysUiStatsLog
@@ -75,10 +77,7 @@ class CommunalMetricsStartableTest : SysuiTestCase() {
// Set up an existing user, which is required for widgets to show
val userInfos = listOf(UserInfo(0, "main", UserInfo.FLAG_MAIN))
userRepository.setUserInfos(userInfos)
- userTracker.set(
- userInfos = userInfos,
- selectedUserIndex = 0,
- )
+ userTracker.set(userInfos = userInfos, selectedUserIndex = 0)
underTest =
CommunalMetricsStartable(
@@ -90,14 +89,16 @@ class CommunalMetricsStartableTest : SysuiTestCase() {
)
}
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
@Test
- fun start_communalFlagDisabled_doNotSetPullAtomCallback() {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ fun start_communalFlagDisabled_doNotSetPullAtomCallback() =
+ kosmos.runTest {
+ fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
- underTest.start()
+ underTest.start()
- verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
- }
+ verify(statsManager, never()).setPullAtomCallback(anyInt(), anyOrNull(), any(), any())
+ }
@Test
fun onPullAtom_atomTagDoesNotMatch_pullSkip() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index b66727e492cf..038ea9ccaaa9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -26,8 +26,8 @@ import android.content.res.mainResources
import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
@@ -35,10 +35,13 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.nullable
@@ -51,15 +54,21 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val kosmos =
testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
private val testScope = kosmos.testScope
private lateinit var underTest: CommunalSettingsRepository
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
@Before
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -164,17 +173,17 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
}
- @EnableFlags(FLAG_COMMUNAL_HUB)
+ @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
fun classicFlagIsDisabled() =
- testScope.runTest {
- kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+ kosmos.runTest {
+ setCommunalV2Enabled(false)
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isFalse()
assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
}
- @DisableFlags(FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
@Test
fun communalHubFlagIsDisabled() =
testScope.runTest {
@@ -295,6 +304,34 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
}
}
+ @Test
+ fun screensaverDisabledByUser() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
+
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ 0,
+ PRIMARY_USER.id,
+ )
+
+ assertThat(enabledState).isFalse()
+ }
+
+ @Test
+ fun screensaverEnabledByUser() =
+ testScope.runTest {
+ val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
+
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ 1,
+ PRIMARY_USER.id,
+ )
+
+ assertThat(enabledState).isTrue()
+ }
+
private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
.thenReturn(disabledFlags)
@@ -310,5 +347,11 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
val WORK_PROFILE =
UserInfo(10, "work", /* iconPath= */ "", /* flags= */ 0, USER_TYPE_PROFILE_MANAGED)
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
new file mode 100644
index 000000000000..c365f1cb3872
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalBackActionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private var Kosmos.underTest by Fixture { communalBackActionInteractor }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ fun communalShowing_canBeDismissed() =
+ kosmos.runTest {
+ setCommunalAvailable(true)
+ assertThat(underTest.canBeDismissed()).isEqualTo(false)
+ communalInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ assertThat(underTest.canBeDismissed()).isEqualTo(true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ fun onBackPressed_invokesSceneChange() =
+ kosmos.runTest {
+ underTest.onBackPressed()
+ runCurrent()
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index b9e646fee98f..7ae0577bd289 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -35,6 +35,7 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
@@ -232,7 +233,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@Test
fun isCommunalAvailable_communalDisabled_false() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
+ mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 0bfcd242828d..8a9c42d9b64e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -130,19 +130,6 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
}
@Test
- fun tutorialState_startedAndCommunalSceneShowing_stateWillNotUpdate() =
- testScope.runTest {
- val tutorialSettingState by
- collectLastValue(communalTutorialRepository.tutorialSettingState)
-
- communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
-
- goToCommunal()
-
- assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
- }
-
- @Test
fun tutorialState_completedAndCommunalSceneShowing_stateWillNotUpdate() =
testScope.runTest {
val tutorialSettingState by
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
index 88206850eb60..b78080885b0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -29,12 +30,16 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
@@ -56,10 +61,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
}
@Test
- fun shouldShowDreamButtonOnHub_trueWhenCanDream() =
+ fun shouldShowDreamButtonOnHub_trueWhenPluggedIn() =
with(kosmos) {
runTest {
- whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
whenever(batteryController.isPluggedIn()).thenReturn(true)
val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
@@ -68,11 +72,10 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
}
@Test
- fun shouldShowDreamButtonOnHub_falseWhenCannotDream() =
+ fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
with(kosmos) {
runTest {
- whenever(dreamManager.canStartDreaming(any())).thenReturn(false)
- whenever(batteryController.isPluggedIn()).thenReturn(true)
+ whenever(batteryController.isPluggedIn()).thenReturn(false)
val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
assertThat(shouldShowButton).isFalse()
@@ -80,25 +83,40 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
}
@Test
- fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() =
+ fun onShowDreamButtonTap_dreamsEnabled_startsDream() =
with(kosmos) {
runTest {
- whenever(dreamManager.canStartDreaming(any())).thenReturn(true)
- whenever(batteryController.isPluggedIn()).thenReturn(false)
+ val currentUser = fakeUserRepository.asMainUser()
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ 1,
+ currentUser.id,
+ )
+ runCurrent()
- val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
- assertThat(shouldShowButton).isFalse()
+ underTest.onShowDreamButtonTap()
+ runCurrent()
+
+ verify(dreamManager).startDream()
}
}
@Test
- fun onShowDreamButtonTap_startsDream() =
+ fun onShowDreamButtonTap_dreamsDisabled_startsActivity() =
with(kosmos) {
runTest {
+ val currentUser = fakeUserRepository.asMainUser()
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ 0,
+ currentUser.id,
+ )
+ runCurrent()
+
underTest.onShowDreamButtonTap()
runCurrent()
- verify(dreamManager).startDream()
+ verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt())
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt
index 928514657257..c8661cf59051 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/ControlActionCoordinatorImplTest.kt
@@ -16,9 +16,9 @@
package com.android.systemui.controls.ui
+import android.view.HapticFeedbackConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import android.view.HapticFeedbackConstants
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.taskview.TaskViewFactory
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,31 +46,20 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
@SmallTest
@RunWith(AndroidJUnit4::class)
class ControlActionCoordinatorImplTest : SysuiTestCase() {
- @Mock
- private lateinit var vibratorHelper: VibratorHelper
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var bgExecutor: DelayableExecutor
- @Mock
- private lateinit var uiExecutor: DelayableExecutor
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var broadcastSender: BroadcastSender
- @Mock
- private lateinit var taskViewFactory: Optional<TaskViewFactory>
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private lateinit var cvh: ControlViewHolder
- @Mock
- private lateinit var metricsLogger: ControlsMetricsLogger
- @Mock
- private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var bgExecutor: DelayableExecutor
+ @Mock private lateinit var uiExecutor: DelayableExecutor
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var broadcastSender: BroadcastSender
+ @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory>
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder
+ @Mock private lateinit var metricsLogger: ControlsMetricsLogger
+ @Mock private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -89,18 +79,21 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
controlsSettingsRepository.setCanShowControlsInLockscreen(true)
- coordinator = spy(ControlActionCoordinatorImpl(
- mContext,
- bgExecutor,
- uiExecutor,
- activityStarter,
- broadcastSender,
- keyguardStateController,
- taskViewFactory,
- metricsLogger,
- vibratorHelper,
- controlsSettingsRepository,
- ))
+ coordinator =
+ spy(
+ ControlActionCoordinatorImpl(
+ mContext,
+ bgExecutor,
+ uiExecutor,
+ activityStarter,
+ broadcastSender,
+ keyguardStateController,
+ taskViewFactory,
+ metricsLogger,
+ vibratorHelper,
+ controlsSettingsRepository,
+ )
+ )
coordinator.activityContext = mContext
`when`(cvh.cws.ci.controlId).thenReturn(ID)
@@ -198,19 +191,15 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
fun drag_isEdge_performsSegmentTickHaptics() {
coordinator.drag(cvh, true)
- verify(vibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.SEGMENT_TICK)
- )
+ verify(vibratorHelper)
+ .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_TICK))
}
@Test
fun drag_isNotEdge_performsFrequentTickHaptics() {
coordinator.drag(cvh, false)
- verify(vibratorHelper).performHapticFeedback(
- any(),
- eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
- )
+ verify(vibratorHelper)
+ .performHapticFeedback(any(), eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index b07097d61b96..5921e9479bd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -92,10 +92,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.isNull
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.firstValue
@@ -768,7 +768,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
runCurrent()
verify(mDreamOverlayCallback).onRedirectWake(true)
client.onWakeRequested()
- verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull())
+ verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), anyOrNull())
verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index c950523f7854..c950523f7854 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/domain/interactor/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 9cfd328a9484..f2a6c11b872e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -36,6 +36,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@@ -43,6 +44,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
+@Ignore("b/384284415")
class ContextualEducationRepositoryTest : SysuiTestCase() {
private lateinit var underTest: UserContextualEducationRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index ee3e241b5754..56e8185ab580 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -81,7 +81,7 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
// Then
verify(cameraGestureHelper)
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true), result)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 50ac26196978..fde9b8ce6a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -197,7 +197,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
val dndMode = currentModes!!.single()
assertThat(dndMode.isActive).isFalse()
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
}
@Test
@@ -222,7 +222,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
)
// then
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
assertEquals(ZEN_MODE_OFF, spyZenMode.value)
assertNull(spyConditionId.value)
}
@@ -244,7 +244,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
val dndMode = currentModes!!.single()
assertThat(dndMode.isActive).isTrue()
assertThat(zenModeRepository.getModeActiveDuration(dndMode.id)).isNull()
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
}
@Test
@@ -268,7 +268,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
)
// then
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value)
assertNull(spyConditionId.value)
}
@@ -285,7 +285,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
val result = underTest.onTriggered(null)
runCurrent()
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
val dndMode = currentModes!!.single()
assertThat(dndMode.isActive).isTrue()
assertThat(zenModeRepository.getModeActiveDuration(dndMode.id))
@@ -313,7 +313,7 @@ class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
)
// then
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, spyZenMode.value)
assertEquals(conditionUri, spyConditionId.value)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt
new file mode 100644
index 000000000000..18946f9d7e07
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.data.quickaffordance
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceHapticViewModelFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class KeyguardQuickAffordanceHapticViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ private val configKey = "$slotId::home"
+ private val keyguardQuickAffordanceInteractor = kosmos.keyguardQuickAffordanceInteractor
+ private val viewModelFlow =
+ MutableStateFlow(KeyguardQuickAffordanceViewModel(configKey = configKey, slotId = slotId))
+
+ private val underTest =
+ kosmos.keyguardQuickAffordanceHapticViewModelFactory.create(viewModelFlow)
+
+ @Test
+ fun whenLaunchingFromTriggeredResult_hapticStateIsLaunch() =
+ testScope.runTest {
+ // GIVEN that the result from triggering the affordance launched an activity or dialog
+ val hapticState by collectLastValue(underTest.quickAffordanceHapticState)
+ keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult(
+ KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(true, configKey)
+ )
+ runCurrent()
+
+ // THEN the haptic state indicates that a launch haptics must play
+ assertThat(hapticState)
+ .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH)
+ }
+
+ @Test
+ fun whenNotLaunchFromTriggeredResult_hapticStateDoesNotEmit() =
+ testScope.runTest {
+ // GIVEN that the result from triggering the affordance did not launch an activity or
+ // dialog
+ val hapticState by collectLastValue(underTest.quickAffordanceHapticState)
+ keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult(
+ KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(false, configKey)
+ )
+ runCurrent()
+
+ // THEN there is no haptic state to play any feedback
+ assertThat(hapticState)
+ .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.NO_HAPTICS)
+ }
+
+ @Test
+ fun onQuickAffordanceTogglesToActivated_hapticStateIsToggleOn() =
+ testScope.runTest {
+ // GIVEN that an affordance toggles from deactivated to activated
+ val hapticState by collectLastValue(underTest.quickAffordanceHapticState)
+ toggleQuickAffordance(on = true)
+
+ // THEN the haptic state reflects that a toggle on haptics should play
+ assertThat(hapticState)
+ .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_ON)
+ }
+
+ @Test
+ fun onQuickAffordanceTogglesToDeactivated_hapticStateIsToggleOff() =
+ testScope.runTest {
+ // GIVEN that an affordance toggles from activated to deactivated
+ val hapticState by collectLastValue(underTest.quickAffordanceHapticState)
+ toggleQuickAffordance(on = false)
+
+ // THEN the haptic state reflects that a toggle off haptics should play
+ assertThat(hapticState)
+ .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_OFF)
+ }
+
+ private fun TestScope.toggleQuickAffordance(on: Boolean) {
+ underTest.updateActivatedHistory(!on)
+ runCurrent()
+ underTest.updateActivatedHistory(on)
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
index b15352bfe6ab..173b4e56075c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -49,14 +49,10 @@ import org.mockito.MockitoAnnotations
class MuteQuickAffordanceConfigTest : SysuiTestCase() {
private lateinit var underTest: MuteQuickAffordanceConfig
- @Mock
- private lateinit var ringerModeTracker: RingerModeTracker
- @Mock
- private lateinit var audioManager: AudioManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var ringerModeTracker: RingerModeTracker
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userFileManager: UserFileManager
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -70,9 +66,12 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() {
whenever(userTracker.userContext).thenReturn(context)
whenever(userFileManager.getSharedPreferences(any(), any(), any()))
- .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE))
+ .thenReturn(
+ context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE)
+ )
- underTest = MuteQuickAffordanceConfig(
+ underTest =
+ MuteQuickAffordanceConfig(
context,
userTracker,
userFileManager,
@@ -81,64 +80,71 @@ class MuteQuickAffordanceConfigTest : SysuiTestCase() {
testScope.backgroundScope,
testDispatcher,
testDispatcher,
- )
+ )
}
@Test
- fun pickerState_volumeFixed_notAvailable() = testScope.runTest {
- //given
- whenever(audioManager.isVolumeFixed).thenReturn(true)
+ fun pickerState_volumeFixed_notAvailable() =
+ testScope.runTest {
+ // given
+ whenever(audioManager.isVolumeFixed).thenReturn(true)
- //when
- val result = underTest.getPickerScreenState()
+ // when
+ val result = underTest.getPickerScreenState()
- //then
- assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result)
- }
+ // then
+ assertEquals(
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice,
+ result,
+ )
+ }
@Test
- fun pickerState_volumeNotFixed_available() = testScope.runTest {
- //given
- whenever(audioManager.isVolumeFixed).thenReturn(false)
+ fun pickerState_volumeNotFixed_available() =
+ testScope.runTest {
+ // given
+ whenever(audioManager.isVolumeFixed).thenReturn(false)
- //when
- val result = underTest.getPickerScreenState()
+ // when
+ val result = underTest.getPickerScreenState()
- //then
- assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result)
- }
+ // then
+ assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result)
+ }
@Test
- fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() = testScope.runTest {
- //given
- val ringerModeCapture = argumentCaptor<Int>()
- whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
- underTest.onTriggered(null)
- whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
-
- //when
- val result = underTest.onTriggered(null)
- runCurrent()
- verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
-
- //then
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
- assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value)
- }
+ fun triggered_stateWasPreviouslyNORMAL_currentlySILENT_moveToPreviousState() =
+ testScope.runTest {
+ // given
+ val ringerModeCapture = argumentCaptor<Int>()
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ underTest.onTriggered(null)
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+
+ // when
+ val result = underTest.onTriggered(null)
+ runCurrent()
+ verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
+
+ // then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
+ assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value)
+ }
@Test
- fun triggered_stateIsNotSILENT_moveToSILENTringer() = testScope.runTest {
- //given
- val ringerModeCapture = argumentCaptor<Int>()
- whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
-
- //when
- val result = underTest.onTriggered(null)
- runCurrent()
- verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
-
- //then
- assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
- assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value)
- }
-} \ No newline at end of file
+ fun triggered_stateIsNotSILENT_moveToSILENTringer() =
+ testScope.runTest {
+ // given
+ val ringerModeCapture = argumentCaptor<Int>()
+ whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+
+ // when
+ val result = underTest.onTriggered(null)
+ runCurrent()
+ verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
+
+ // then
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false), result)
+ assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index e9b36b8b3b57..9bdc363b3a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -88,9 +88,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
Icon.Loaded(
drawable = ICON,
contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
+ ContentDescription.Resource(res = R.string.accessibility_wallet_button),
)
)
}
@@ -118,9 +116,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
Icon.Loaded(
drawable = ICON,
contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
+ ContentDescription.Resource(res = R.string.accessibility_wallet_button),
)
)
}
@@ -163,13 +159,9 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
assertThat(underTest.onTriggered(expandable))
- .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true))
verify(walletController)
- .startQuickAccessUiIntent(
- activityStarter,
- animationController,
- /* hasCard= */ true,
- )
+ .startQuickAccessUiIntent(activityStarter, animationController, /* hasCard= */ true)
}
@Test
@@ -184,9 +176,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_unavailable() =
testScope.runTest {
- setUpState(
- isWalletServiceAvailable = false,
- )
+ setUpState(isWalletServiceAvailable = false)
assertThat(underTest.getPickerScreenState())
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
@@ -195,9 +185,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() =
testScope.runTest {
- setUpState(
- isWalletFeatureAvailable = false,
- )
+ setUpState(isWalletFeatureAvailable = false)
assertThat(underTest.getPickerScreenState())
.isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
@@ -206,9 +194,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_disabledWhenThereIsNoCard() =
testScope.runTest {
- setUpState(
- hasSelectedCard = false,
- )
+ setUpState(hasSelectedCard = false)
assertThat(underTest.getPickerScreenState())
.isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
@@ -219,7 +205,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
hasSelectedCard: Boolean = true,
- cardType: Int = WalletCard.CARD_TYPE_UNKNOWN
+ cardType: Int = WalletCard.CARD_TYPE_UNKNOWN,
) {
val walletClient: QuickAccessWalletClient = mock()
whenever(walletClient.tileIcon).thenReturn(ICON)
@@ -242,11 +228,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
/*cardType= */ cardType,
/*cardImage= */ mock(),
/*contentDescription= */ CARD_DESCRIPTION,
- /*pendingIntent= */ mock()
+ /*pendingIntent= */ mock(),
)
.build()
),
- 0
+ 0,
)
} else {
GetWalletCardsResponse(emptyList(), 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 46d1ebe75899..9de0215022e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -764,6 +764,28 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(launchingAffordance).isFalse()
}
+ @Test
+ fun onQuickAffordanceTriggered_updatesLaunchingFromTriggeredResult() =
+ testScope.runTest {
+ // WHEN selecting and triggering a quick affordance at a slot
+ val key = homeControls.key
+ val slot = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ val encodedKey = "$slot::$key"
+ val actionLaunched = true
+ homeControls.onTriggeredResult =
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(actionLaunched)
+ underTest.select(slot, key)
+ runCurrent()
+ underTest.onQuickAffordanceTriggered(encodedKey, expandable = null, slot)
+
+ // THEN the latest triggered result shows that an action launched for the same key and
+ // slot
+ val launchingFromTriggeredResult by
+ collectLastValue(underTest.launchingFromTriggeredResult)
+ assertThat(launchingFromTriggeredResult?.launched).isEqualTo(actionLaunched)
+ assertThat(launchingFromTriggeredResult?.configKey).isEqualTo(encodedKey)
+ }
+
companion object {
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
private val ICON: Icon =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
index ad5eeabf83d2..26fe379f00bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -118,6 +119,50 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
}
+ /** STL: Ls -> overlay, then settle with Idle(overlay). */
+ @Test
+ fun transition_overlay_from_ls_scene_end_in_gone() =
+ testScope.runTest {
+ sceneTransitions.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Scenes.Lockscreen,
+ toContent = Overlays.NotificationsShade,
+ currentScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(emptySet()),
+ progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Idle(
+ Scenes.Lockscreen,
+ setOf(Overlays.NotificationsShade),
+ )
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
/**
* STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the
* transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to
@@ -259,6 +304,47 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
}
+ /** STL: Ls with overlay, then settle with Idle(Ls). */
+ @Test
+ fun transition_overlay_to_ls_scene_end_in_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value =
+ ObservableTransitionState.Transition.ShowOrHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ fromContent = Overlays.NotificationsShade,
+ toContent = Scenes.Lockscreen,
+ currentScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(setOf(Overlays.NotificationsShade)),
+ progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
/** STL: Gone -> Ls (AOD), will transition to AOD once */
@Test
fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 0e3b03f74c02..be504cc0f704 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -18,11 +18,8 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
@@ -75,7 +72,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
private val burnInFlow = MutableStateFlow(BurnInModel())
@Before
- @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@DisableSceneContainer
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -219,50 +215,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
- testScope.runTest {
- underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
- val movement by collectLastValue(underTest.movement)
- assertThat(movement?.translationX).isEqualTo(0)
-
- // Set to dozing (on AOD)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- ),
- validateStep = false,
- )
-
- // Trigger a change to the burn-in model
- burnInFlow.value = BurnInModel(translationX = 20, translationY = -30, scale = 0.5f)
- assertThat(movement?.translationX).isEqualTo(20)
- // -20 instead of -30, due to inset of 80
- assertThat(movement?.translationY).isEqualTo(-20)
- assertThat(movement?.scale).isEqualTo(0.5f)
- assertThat(movement?.scaleClockOnly).isEqualTo(true)
-
- // Set to the beginning of GONE->AOD transition
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- ),
- validateStep = false,
- )
- assertThat(movement?.translationX).isEqualTo(0)
- assertThat(movement?.translationY).isEqualTo(0)
- assertThat(movement?.scale).isEqualTo(1f)
- assertThat(movement?.scaleClockOnly).isEqualTo(true)
- }
-
- @Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
testScope.runTest {
underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
@@ -334,7 +286,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_weatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
@@ -344,7 +295,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_weatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
@@ -354,7 +304,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
isSmallClock = false,
@@ -364,7 +313,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@Test
@DisableSceneContainer
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun translationAndScale_sceneContainerOff_nonWeatherSmallClock() =
testBurnInViewModelForClocks(
isSmallClock = true,
@@ -373,7 +321,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_weatherLargeClock() =
testBurnInViewModelForClocks(
@@ -383,7 +330,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_weatherSmallClock() =
testBurnInViewModelForClocks(
@@ -393,7 +339,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
fun translationAndScale_sceneContainerOn_nonWeatherLargeClock() =
testBurnInViewModelForClocks(
@@ -403,7 +348,6 @@ class AodBurnInViewModelTest : SysuiTestCase() {
)
@Test
- @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
@EnableSceneContainer
@Ignore("b/367659687")
fun translationAndScale_sceneContainerOn_nonWeatherSmallClock() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 95ffc962797d..789477e38b55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -19,12 +19,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -73,7 +71,6 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 004aeb069a14..3d9fb0a9be16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -56,6 +56,7 @@ import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -84,6 +85,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
FakeExecutor mMainExecutor;
FakeExecutor mBackgroundExecutor;
DeviceConfigProxyFake mDeviceConfigProxyFake;
+ FakeShadeDialogContextInteractor mFakeShadeDialogContextInteractor;
@Mock
IActivityManager mIActivityManager;
@@ -118,6 +120,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mFakeShadeDialogContextInteractor = new FakeShadeDialogContextInteractor(mContext);
mDeviceConfigProxyFake = new DeviceConfigProxyFake();
mSystemClock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(mSystemClock);
@@ -346,7 +349,6 @@ public class FgsManagerControllerTest extends SysuiTestCase {
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"true", false);
FgsManagerController fmc = new FgsManagerControllerImpl(
- mContext,
mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
@@ -359,7 +361,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mDialogTransitionAnimator,
mBroadcastDispatcher,
mDumpManager,
- mSystemUIDialogFactory
+ mSystemUIDialogFactory,
+ mFakeShadeDialogContextInteractor
);
fmc.init();
Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
@@ -374,7 +377,6 @@ public class FgsManagerControllerTest extends SysuiTestCase {
SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
"false", false);
fmc = new FgsManagerControllerImpl(
- mContext,
mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
@@ -387,7 +389,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mDialogTransitionAnimator,
mBroadcastDispatcher,
mDumpManager,
- mSystemUIDialogFactory
+ mSystemUIDialogFactory,
+ mFakeShadeDialogContextInteractor
);
fmc.init();
Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
@@ -487,7 +490,6 @@ public class FgsManagerControllerTest extends SysuiTestCase {
ArgumentCaptor.forClass(BroadcastReceiver.class);
FgsManagerController result = new FgsManagerControllerImpl(
- mContext,
mContext.getResources(),
mMainExecutor,
mBackgroundExecutor,
@@ -500,7 +502,8 @@ public class FgsManagerControllerTest extends SysuiTestCase {
mDialogTransitionAnimator,
mBroadcastDispatcher,
mDumpManager,
- mSystemUIDialogFactory
+ mSystemUIDialogFactory,
+ mFakeShadeDialogContextInteractor
);
result.init();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
index a8e390c25a4d..a8e390c25a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index fbbdc46a7873..0e823ccf1df5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
import com.android.systemui.util.mockito.whenever
@@ -96,6 +97,7 @@ class DataSaverTileTest(flags: FlagsParameterization) : SysuiTestCase() {
dataSaverController,
mDialogTransitionAnimator,
systemUIDialogFactory,
+ FakeShadeDialogContextInteractor(mContext),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index 056efb34a0b1..c47a412e226a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -117,7 +117,6 @@ class QSTileViewModelImplTest : SysuiTestCase() {
"test_spec:\n" +
" QSTileState(" +
"icon=Resource(res=0, contentDescription=Resource(res=0)), " +
- "iconRes=null, " +
"label=test_data, " +
"activationState=INACTIVE, " +
"secondaryLabel=null, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
index b88861756889..5c6657b98801 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -231,7 +232,8 @@ public class InternetAdapterTest extends SysuiTestCase {
mViewHolder.onWifiClick(mWifiEntry, mock(View.class));
- verify(mSpyContext).startActivity(any());
+ verify(mInternetDialogController).startActivityForDialog(any());
+ verify(mSpyContext, never()).startActivity(any());
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 00460bfe83b2..557f4ea262a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -93,8 +93,7 @@ class AirplaneModeMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.airplane_mode)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 632aae035ede..24e46686e23d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -178,8 +178,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.status_bar_alarm)
return QSTileState(
- Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
- R.drawable.ic_alarm,
+ Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null, R.drawable.ic_alarm),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index 5385f945946c..2ddaddd5ad35 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,8 +253,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.battery_detail_switch_title)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index 356b98eb192e..a3c159820a94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -77,8 +77,11 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_color_correction_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
- R.drawable.ic_qs_color_correction,
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_color_correction)!!,
+ null,
+ R.drawable.ic_qs_color_correction,
+ ),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index 8236c4c1e638..608adf183163 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -253,7 +253,6 @@ class CustomTileMapperTest : SysuiTestCase() {
): QSTileState {
return QSTileState(
icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
- null,
"test label",
activationState,
"test subtitle",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 587585ccee2e..a115c1235210 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -73,7 +73,11 @@ class FlashlightMapperTest : SysuiTestCase() {
mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
val expectedIcon =
- Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
+ Icon.Loaded(
+ context.getDrawable(R.drawable.qs_flashlight_icon_on)!!,
+ null,
+ R.drawable.qs_flashlight_icon_on,
+ )
val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -84,7 +88,11 @@ class FlashlightMapperTest : SysuiTestCase() {
mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
val expectedIcon =
- Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+ Icon.Loaded(
+ context.getDrawable(R.drawable.qs_flashlight_icon_off)!!,
+ null,
+ R.drawable.qs_flashlight_icon_off,
+ )
val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -95,7 +103,11 @@ class FlashlightMapperTest : SysuiTestCase() {
mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
val expectedIcon =
- Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+ Icon.Loaded(
+ context.getDrawable(R.drawable.qs_flashlight_icon_off)!!,
+ null,
+ R.drawable.qs_flashlight_icon_off,
+ )
val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index e81771ec38d5..8f8f7105415f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -58,8 +58,11 @@ class FontScalingTileMapperTest : SysuiTestCase() {
private fun createFontScalingTileState(): QSTileState =
QSTileState(
- Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
- R.drawable.ic_qs_font_scaling,
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_font_scaling)!!,
+ null,
+ R.drawable.ic_qs_font_scaling,
+ ),
context.getString(R.string.quick_settings_font_scaling_label),
QSTileState.ActivationState.ACTIVE,
null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index 12d604ff6a7c..3d3447da15a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,8 +102,7 @@ class HearingDevicesTileMapperTest : SysuiTestCase() {
val label = context.getString(R.string.quick_settings_hearing_devices_label)
val iconRes = R.drawable.qs_hearing_devices_icon
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index 9dcf49e02697..b087bbc29bf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -82,7 +82,6 @@ class InternetTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.ACTIVE,
context.getString(R.string.quick_settings_networks_available),
Icon.Loaded(signalDrawable, null),
- null,
context.getString(R.string.quick_settings_internet_label),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -120,8 +119,11 @@ class InternetTileMapperTest : SysuiTestCase() {
createInternetTileState(
QSTileState.ActivationState.ACTIVE,
inputModel.secondaryLabel.loadText(context).toString(),
- Icon.Loaded(context.getDrawable(expectedSatIcon!!.res)!!, null),
- expectedSatIcon.res,
+ Icon.Loaded(
+ context.getDrawable(expectedSatIcon!!.res)!!,
+ null,
+ expectedSatIcon.res,
+ ),
expectedSatIcon.contentDescription.loadContentDescription(context).toString(),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -144,8 +146,7 @@ class InternetTileMapperTest : SysuiTestCase() {
createInternetTileState(
QSTileState.ActivationState.ACTIVE,
context.getString(R.string.quick_settings_networks_available),
- Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
- wifiRes,
+ Icon.Loaded(context.getDrawable(wifiRes)!!, null, wifiRes),
context.getString(R.string.quick_settings_internet_label),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -171,8 +172,8 @@ class InternetTileMapperTest : SysuiTestCase() {
Icon.Loaded(
context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
contentDescription = null,
+ R.drawable.ic_qs_no_internet_unavailable,
),
- R.drawable.ic_qs_no_internet_unavailable,
context.getString(R.string.quick_settings_networks_unavailable),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
@@ -182,13 +183,11 @@ class InternetTileMapperTest : SysuiTestCase() {
activationState: QSTileState.ActivationState,
secondaryLabel: String,
icon: Icon,
- iconRes: Int? = null,
contentDescription: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
return QSTileState(
icon,
- iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index 30fce73e04da..780d58c83e5b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -90,8 +90,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_inversion_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index 37e8a6053682..4ebe23dcdef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -69,7 +69,12 @@ class LocationTileMapperTest : SysuiTestCase() {
fun mapsEnabledDataToOnIconState() {
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
- val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(R.drawable.qs_location_icon_on)!!,
+ null,
+ R.drawable.qs_location_icon_on,
+ )
val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -78,7 +83,12 @@ class LocationTileMapperTest : SysuiTestCase() {
fun mapsDisabledDataToOffIconState() {
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
- val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(R.drawable.qs_location_icon_off)!!,
+ null,
+ R.drawable.qs_location_icon_off,
+ )
val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index de3dc5730421..44e6b4d2d0f6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -28,6 +28,7 @@ import com.android.internal.R
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -63,7 +64,7 @@ class ModesTileDataInteractorTest : SysuiTestCase() {
fun setUp() {
context.orCreateTestableResources.apply {
addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE)
- addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
+ addOverride(BEDTIME_DRAWABLE_ID, BEDTIME_DRAWABLE)
}
val customPackageContext = SysuiTestableContext(context)
@@ -145,24 +146,24 @@ class ModesTileDataInteractorTest : SysuiTestCase() {
// Tile starts with the generic Modes icon.
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
// Add an inactive mode -> Still modes icon
zenModeRepository.addMode(id = "Mode", active = false)
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
// Add an active mode with a default icon: icon should be the mode icon, and the
// iconResId is also populated, because we know it's a system icon.
zenModeRepository.addMode(
id = "Bedtime with default icon",
type = AutomaticZenRule.TYPE_BEDTIME,
- active = true
+ active = true,
)
runCurrent()
assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
- assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+ assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID)
// Add another, less-prioritized mode that has a *custom* icon: for now, icon should
// remain the first mode icon
@@ -177,20 +178,20 @@ class ModesTileDataInteractorTest : SysuiTestCase() {
)
runCurrent()
assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
- assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+ assertThat(tileData?.icon!!.res).isEqualTo(BEDTIME_DRAWABLE_ID)
// Deactivate more important mode: icon should be the less important, still active mode
// And because it's a package-provided icon, iconResId is not populated.
zenModeRepository.deactivateMode("Bedtime with default icon")
runCurrent()
assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON)
- assertThat(tileData?.iconResId).isNull()
+ assertThat(tileData?.icon!!.res).isNull()
// Deactivate remaining mode: back to the default modes icon
zenModeRepository.deactivateMode("Driving with custom icon")
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
}
@Test
@@ -205,18 +206,18 @@ class ModesTileDataInteractorTest : SysuiTestCase() {
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
// Activate a Mode -> Icon doesn't change.
zenModeRepository.addMode(id = "Mode", active = true)
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
zenModeRepository.deactivateMode(id = "Mode")
runCurrent()
assertThat(tileData?.icon).isEqualTo(MODES_ICON)
- assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+ assertThat(tileData?.icon!!.res).isEqualTo(MODES_DRAWABLE_ID)
}
@EnableFlags(Flags.FLAG_MODES_UI)
@@ -256,15 +257,17 @@ class ModesTileDataInteractorTest : SysuiTestCase() {
val TEST_USER = UserHandle.of(1)!!
const val CUSTOM_PACKAGE = "com.some.mode.owner.package"
- val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
+ const val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
const val CUSTOM_DRAWABLE_ID = 12345
+ const val BEDTIME_DRAWABLE_ID = R.drawable.ic_zen_mode_type_bedtime
+
val MODES_DRAWABLE = TestStubDrawable("modes_icon")
val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
val CUSTOM_DRAWABLE = TestStubDrawable("custom")
- val MODES_ICON = MODES_DRAWABLE.asIcon()
- val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon()
+ val MODES_ICON = Icon.Loaded(MODES_DRAWABLE, null, MODES_DRAWABLE_ID)
+ val BEDTIME_ICON = Icon.Loaded(BEDTIME_DRAWABLE, null, BEDTIME_DRAWABLE_ID)
val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index 88b00468573f..04e094f25f5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -156,6 +156,10 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() {
}
private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel {
- return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123)
+ return ModesTileModel(
+ isActivated,
+ activeModeNames,
+ TestStubDrawable("icon").asIcon(res = 123),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index 4e91d16bf1ec..d73044f6b479 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -99,18 +99,11 @@ class ModesTileMapperTest : SysuiTestCase() {
@Test
fun state_modelHasIconResId_includesIconResId() {
- val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = false,
- activeModes = emptyList(),
- icon = icon,
- iconResId = 123,
- )
+ val icon = TestStubDrawable("res123").asIcon(res = 123)
+ val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
val state = underTest.map(config, model)
assertThat(state.icon).isEqualTo(icon)
- assertThat(state.iconRes).isEqualTo(123)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 1457f533f5ec..7c853261aa1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -289,8 +289,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
index 2ac3e081b8f4..b6caa22a3012 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapperTest.kt
@@ -58,8 +58,11 @@ class NotesTileMapperTest : SysuiTestCase() {
private fun createNotesTileState(): QSTileState =
QSTileState(
- Icon.Loaded(context.getDrawable(R.drawable.ic_qs_notes)!!, null),
- R.drawable.ic_qs_notes,
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_notes)!!,
+ null,
+ R.drawable.ic_qs_notes,
+ ),
context.getString(R.string.quick_settings_notes_label),
QSTileState.ActivationState.INACTIVE,
/* secondaryLabel= */ null,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 7782d2b279a8..5b39810e3477 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -66,11 +66,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
val outputState = mapper.map(config, inputModel)
val expectedState =
- createOneHandedModeTileState(
- QSTileState.ActivationState.INACTIVE,
- subtitleArray[1],
- com.android.internal.R.drawable.ic_qs_one_handed_mode,
- )
+ createOneHandedModeTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1])
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -81,23 +77,21 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
val outputState = mapper.map(config, inputModel)
val expectedState =
- createOneHandedModeTileState(
- QSTileState.ActivationState.ACTIVE,
- subtitleArray[2],
- com.android.internal.R.drawable.ic_qs_one_handed_mode,
- )
+ createOneHandedModeTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2])
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
private fun createOneHandedModeTileState(
activationState: QSTileState.ActivationState,
secondaryLabel: String,
- iconRes: Int,
): QSTileState {
val label = context.getString(R.string.quick_settings_onehanded_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(
+ context.getDrawable(com.android.internal.R.drawable.ic_qs_one_handed_mode)!!,
+ null,
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
+ ),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index ed33250a3392..c572ff60b61a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -93,8 +93,8 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
Icon.Loaded(
context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
null,
+ com.android.systemui.res.R.drawable.ic_qr_code_scanner,
),
- com.android.systemui.res.R.drawable.ic_qr_code_scanner,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index 85111fd07663..00017f9059de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -85,8 +85,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
R.drawable.qs_extra_dim_icon_on
else R.drawable.qs_extra_dim_icon_off
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
context.resources
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 53671ba38eb6..74010143166b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -180,8 +180,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9a450653aa8f..1fb5048dd4c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -91,8 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() {
else context.resources.getStringArray(R.array.tile_states_saver)[0]
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index cd683c44a59c..363255695131 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -110,8 +110,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
val label = context.getString(R.string.quick_settings_screen_record_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index c569403960d0..e4cd0e0ec215 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -146,8 +146,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
else context.getString(R.string.quick_settings_mic_label)
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index 0d2ebe42b7ad..8f5f2d3e6689 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -69,8 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
expandedAccessibilityClass: KClass<out View>? = Switch::class,
): QSTileState {
return QSTileState(
- Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes,
+ Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index 86321ea04703..2c81f39a03ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -109,8 +109,7 @@ class WorkModeTileMapperTest : SysuiTestCase() {
val label = testLabel
val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
return QSTileState(
- icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
- iconRes = iconRes,
+ icon = Icon.Loaded(context.getDrawable(iconRes)!!, null, iconRes),
label = label,
activationState = activationState,
secondaryLabel =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 039a1dc05c79..518a0a5a1446 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -84,6 +85,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
mDialogTransitionAnimator,
uiEventLogger,
dialogFactory,
+ FakeShadeDialogContextInteractor(mContext),
)
}
@@ -91,32 +93,32 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun showDialog_callsDialogShow() {
val launchController = mock<DialogTransitionAnimator.Controller>()
`when`(launchExpandable.dialogTransitionController(any())).thenReturn(launchController)
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(mDialogTransitionAnimator).show(eq(dialog), eq(launchController), anyBoolean())
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@Test
fun dialog_showForAllUsers() {
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(dialog).setShowForAllUsers(true)
}
@Test
fun dialog_cancelOnTouchOutside() {
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(dialog).setCanceledOnTouchOutside(true)
}
@Test
fun adapterAndGridLinked() {
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
}
@Test
fun doneButtonLogsCorrectly() {
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(dialog).setPositiveButton(anyInt(), capture(clickCaptor))
@@ -129,7 +131,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_noFalsing_opensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
@@ -150,7 +152,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
fun clickSettingsButton_Falsing_notOpensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
- controller.showDialog(context, launchExpandable)
+ controller.showDialog(launchExpandable)
verify(dialog)
.setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
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 e56b965d9402..3187cca6ca45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -37,7 +37,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -48,9 +47,9 @@ import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.currentValue
-import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.verifyCurrent
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -77,12 +76,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
/**
* Integration test cases for the Scene Framework.
@@ -137,10 +133,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneContainerViewModel.activateIn(testScope)
assertWithMessage("Initial scene key mismatch!")
- .that(sceneContainerViewModel.currentScene.value)
+ .that(currentValue(sceneContainerViewModel.currentScene))
.isEqualTo(sceneContainerConfig.initialSceneKey)
assertWithMessage("Initial scene container visibility mismatch!")
- .that(sceneContainerViewModel.isVisible)
+ .that(currentValue { sceneContainerViewModel.isVisible })
.isTrue()
}
@@ -337,7 +333,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(bouncerActionButton)
.isNotNull()
kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
- runCurrent()
// TODO(b/369765704): Assert that an activity was started once we use ActivityStarter.
}
@@ -358,9 +353,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(bouncerActionButton)
.isNotNull()
kosmos.bouncerSceneContentViewModel.onActionButtonClicked(bouncerActionButton!!)
- runCurrent()
- verify(mockTelecomManager).showInCallScreen(any())
+ verifyCurrent(mockTelecomManager).showInCallScreen(any())
}
@Test
@@ -413,7 +407,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
* the UI must gradually transition between scenes.
*/
private fun Kosmos.getCurrentSceneInUi(): SceneKey {
- return when (val state = transitionState.value) {
+ return when (val state = currentValue(transitionState)) {
is ObservableTransitionState.Idle -> state.currentScene
is ObservableTransitionState.Transition.ChangeScene -> state.fromScene
is ObservableTransitionState.Transition.ShowOrHideOverlay -> state.currentScene
@@ -436,7 +430,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
// is not an observable that can trigger a new evaluation.
fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
- testScope.runCurrent()
}
/** Emulates a phone call in progress. */
@@ -447,7 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
setIsInCall(true)
setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
}
- testScope.runCurrent()
}
/**
@@ -480,24 +472,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
isInitiatedByUserInput = false,
isUserInputOngoing = flowOf(false),
)
- testScope.runCurrent()
// Report progress of transition.
- while (progressFlow.value < 1f) {
+ while (currentValue(progressFlow) < 1f) {
progressFlow.value += 0.2f
- testScope.runCurrent()
}
// End the transition and report the change.
transitionState.value = ObservableTransitionState.Idle(to)
fakeSceneDataSource.unpause(force = true)
- testScope.runCurrent()
assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
- .that(sceneContainerViewModel.isVisible)
+ .that(currentValue { sceneContainerViewModel.isVisible })
.isEqualTo(expectedVisible)
- assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
+ assertThat(currentValue(sceneContainerViewModel.currentScene)).isEqualTo(to)
bouncerSceneJob =
if (to == Scenes.Bouncer) {
@@ -510,7 +499,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
bouncerSceneJob?.cancel()
null
}
- testScope.runCurrent()
}
/**
@@ -556,13 +544,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
)
powerInteractor.setAwakeForTest()
- testScope.runCurrent()
}
/** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
private fun Kosmos.unlockDevice() {
assertWithMessage("Cannot unlock a device that's already unlocked!")
- .that(deviceEntryInteractor.isUnlocked.value)
+ .that(currentValue(deviceEntryInteractor.isUnlocked))
.isFalse()
emulateUserDrivenTransition(Scenes.Bouncer)
@@ -595,7 +582,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
pinBouncerViewModel.onPinButtonClicked(digit)
}
pinBouncerViewModel.onAuthenticateButtonClicked()
- testScope.runCurrent()
}
/**
@@ -625,26 +611,23 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
pinBouncerViewModel.onAuthenticateButtonClicked()
fakeMobileConnectionsRepository.isAnySimSecure.value = false
- testScope.runCurrent()
setAuthMethod(authMethodAfterSimUnlock, enableLockscreen)
- testScope.runCurrent()
}
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
private fun Kosmos.wakeUpDevice() {
- val wakefulnessModel = powerInteractor.detailedWakefulness.value
+ val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
assertWithMessage("Cannot wake up device as it's already awake!")
.that(wakefulnessModel.isAwake())
.isFalse()
powerInteractor.setAwakeForTest()
- testScope.runCurrent()
}
/** Changes device wakefulness state from awake to asleep, going through intermediary states. */
private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) {
- val wakefulnessModel = powerInteractor.detailedWakefulness.value
+ val wakefulnessModel = currentValue(powerInteractor.detailedWakefulness)
assertWithMessage("Cannot put device to sleep as it's already asleep!")
.that(wakefulnessModel.isAwake())
.isTrue()
@@ -659,22 +642,18 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
)
.toLong()
)
- } else {
- testScope.runCurrent()
}
}
/** Emulates the dismissal of the IME (soft keyboard). */
private fun Kosmos.dismissIme() {
- (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
- it.onImeDismissed()
- testScope.runCurrent()
- }
+ (currentValue(bouncerSceneContentViewModel.authMethodViewModel)
+ as? PasswordBouncerViewModel)
+ ?.let { it.onImeDismissed() }
}
private fun Kosmos.introduceLockedSim() {
setAuthMethod(AuthenticationMethodModel.Sim)
fakeMobileConnectionsRepository.isAnySimSecure.value = true
- testScope.runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 929537dcf757..0361ffe475a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -212,18 +212,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testDragDownHelperCalledWhenDraggingDown() =
- testScope.runTest {
- whenever(dragDownHelper.isDraggingDown).thenReturn(true)
- val now = SystemClock.elapsedRealtime()
- val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
- underTest.onTouchEvent(ev)
- verify(dragDownHelper).onTouchEvent(ev)
- ev.recycle()
- }
-
- @Test
fun testNoInterceptTouch() =
testScope.runTest {
captureInteractionEventHandler()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index aa8b4f136683..4423426945eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -117,7 +117,6 @@ class DefaultClockProviderTest : SysuiTestCase() {
verify(mockLargeClockView).onTimeZoneChanged(notNull())
verify(mockSmallClockView).refreshTime()
verify(mockLargeClockView).refreshTime()
- verify(mockLargeClockView).setLayoutParams(any())
}
@Test
@@ -163,7 +162,6 @@ class DefaultClockProviderTest : SysuiTestCase() {
clock.largeClock.events.onFontSettingChanged(200f)
verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
- verify(mockLargeClockView).setLayoutParams(any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 165e943a0cc0..40f13bbbf908 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -71,6 +73,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noNotifs_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -81,6 +84,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_notifMissingStatusBarChipIconView_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -99,6 +103,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -122,6 +127,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -145,6 +151,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_colorMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -175,6 +182,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onlyForPromotedNotifs() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -208,6 +216,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -242,6 +251,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_hasShortCriticalText_usesTextInsteadOfTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -272,6 +282,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noTime_isIconOnly() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -294,6 +305,36 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_basicTime_hiddenIfAutomaticallyPromoted() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_basicTime_isShortTimeDelta() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -322,6 +363,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_countUpTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -349,6 +391,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_countDownTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -376,6 +419,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_noHeadsUp_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -407,6 +451,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_hasHeadsUpByUser_onlyShowsIcon() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -442,6 +487,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_clickingChipNotifiesInteractor() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
new file mode 100644
index 000000000000..14787e169979
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarPopupChips
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnableFlags(StatusBarPopupChips.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.statusBarPopupChipsViewModel
+
+ @Test
+ fun popupChips_allHidden_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.popupChips)
+ assertThat(latest).isEmpty()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index abd0a284ae3d..3359db0a22e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -43,7 +43,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class PromotedNotificationContentExtractorTest : SysuiTestCase() {
+class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val provider =
@@ -54,7 +54,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun shouldNotExtract_bothFlagsDisabled() {
- val notif = createEntry().also { provider.promotedEntries.add(it) }
+ val notif = createEntry()
val content = extractContent(notif)
assertThat(content).isNull()
}
@@ -63,7 +63,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
fun shouldExtract_promotedNotificationUiFlagEnabled() {
- val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val entry = createEntry()
val content = extractContent(entry)
assertThat(content).isNotNull()
}
@@ -72,7 +72,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
fun shouldExtract_statusBarNotifChipsFlagEnabled() {
- val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val entry = createEntry()
val content = extractContent(entry)
assertThat(content).isNotNull()
}
@@ -80,7 +80,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun shouldExtract_bothFlagsEnabled() {
- val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val entry = createEntry()
val content = extractContent(entry)
assertThat(content).isNotNull()
}
@@ -88,22 +88,19 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun shouldNotExtract_providerDidNotPromote() {
- val entry = createEntry().also { provider.promotedEntries.remove(it) }
+ val entry = createEntry(promoted = false)
val content = extractContent(entry)
assertThat(content).isNull()
}
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_commonFields() {
- val entry =
- createEntry {
- setSubText(TEST_SUB_TEXT)
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- setShortCriticalText(TEST_SHORT_CRITICAL_TEXT)
- }
- .also { provider.promotedEntries.add(it) }
+ fun extractsContent_commonFields() {
+ val entry = createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
val content = extractContent(entry)
@@ -117,9 +114,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
@DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
- val entry =
- createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- .also { provider.promotedEntries.add(it) }
+ val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
val content = extractContent(entry)
@@ -134,9 +129,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
android.app.Flags.FLAG_API_RICH_ONGOING,
)
fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
- val entry =
- createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- .also { provider.promotedEntries.add(it) }
+ val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
val content = extractContent(entry)
@@ -151,7 +144,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
android.app.Flags.FLAG_API_RICH_ONGOING,
)
fun extractContent_noShortCriticalTextSet_textIsNull() {
- val entry = createEntry {}.also { provider.promotedEntries.add(it) }
+ val entry = createEntry { setShortCriticalText(null) }
val content = extractContent(entry)
@@ -161,9 +154,8 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBigPictureStyle() {
- val entry =
- createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) }
+ fun extractsContent_fromBigPictureStyle() {
+ val entry = createEntry { setStyle(BigPictureStyle()) }
val content = extractContent(entry)
@@ -174,8 +166,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBigTextStyle() {
- val entry =
- createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) }
+ val entry = createEntry { setStyle(BigTextStyle()) }
val content = extractContent(entry)
@@ -187,11 +178,13 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromCallStyle() {
val hangUpIntent =
- PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE)
-
- val entry =
- createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
- .also { provider.promotedEntries.add(it) }
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ Intent("hangup_action"),
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
val content = extractContent(entry)
@@ -202,11 +195,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromProgressStyle() {
- val entry =
- createEntry {
- setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
- }
- .also { provider.promotedEntries.add(it) }
+ val entry = createEntry {
+ setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
+ }
val content = extractContent(entry)
@@ -220,13 +211,9 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromIneligibleStyle() {
- val entry =
- createEntry {
- setStyle(
- MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)
- )
- }
- .also { provider.promotedEntries.add(it) }
+ val entry = createEntry {
+ setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
+ }
val content = extractContent(entry)
@@ -239,9 +226,14 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
return underTest.extractContent(entry, recoveredBuilder)
}
- private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry {
- val notif = Notification.Builder(context, "a").also(builderBlock).build()
- return NotificationEntryBuilder().setNotification(notif).build()
+ private fun createEntry(
+ promoted: Boolean = true,
+ builderBlock: Notification.Builder.() -> Unit = {},
+ ): NotificationEntry {
+ val notif = Notification.Builder(context, "channel").also(builderBlock).build()
+ return NotificationEntryBuilder().setNotification(notif).build().also {
+ provider.shouldPromoteForEntry[it] = promoted
+ }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 6eb2764165b5..a49a66fe26b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -30,14 +36,15 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
+import android.app.Person;
import android.content.Context;
+import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -61,12 +68,13 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -105,7 +113,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
@Mock private HeadsUpStyleProvider mHeadsUpStyleProvider;
@Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
- @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
+ private final FakePromotedNotificationContentExtractor mPromotedNotificationContentExtractor =
+ new FakePromotedNotificationContentExtractor();
private final SmartReplyStateInflater mSmartReplyStateInflater =
new SmartReplyStateInflater() {
@@ -155,8 +164,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Test
public void testIncreasedHeadsUpBeingUsed() {
- BindParams params = new BindParams();
- params.usesIncreasedHeadsUpHeight = true;
+ BindParams params = new BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true,
+ REDACTION_TYPE_NONE);
Notification.Builder builder = spy(mBuilder);
mNotificationInflater.inflateNotificationViews(
mRow.getEntry(),
@@ -166,14 +175,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
+ mContext,
mSmartReplyStateInflater);
verify(builder).createHeadsUpContentView(true);
}
@Test
public void testIncreasedHeightBeingUsed() {
- BindParams params = new BindParams();
- params.usesIncreasedHeight = true;
+ BindParams params = new BindParams(false, /* usesIncreasedHeight */ true, false,
+ REDACTION_TYPE_NONE);
Notification.Builder builder = spy(mBuilder);
mNotificationInflater.inflateNotificationViews(
mRow.getEntry(),
@@ -183,6 +193,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
+ mContext,
mSmartReplyStateInflater);
verify(builder).createContentView(true);
}
@@ -207,7 +218,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mRow.getEntry().getSbn().getNotification().contentView
= new RemoteViews(mContext.getPackageName(), com.android.systemui.res.R.layout.status_bar);
inflateAndWait(true /* expectingException */, mNotificationInflater, FLAG_CONTENT_VIEW_ALL,
- mRow);
+ REDACTION_TYPE_NONE, mRow);
assertTrue(mRow.getPrivateLayout().getChildCount() == 0);
verify(mRow, times(0)).onNotificationUpdated();
}
@@ -227,7 +238,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mRow.getEntry(),
mRow,
FLAG_CONTENT_VIEW_ALL,
- new BindParams(),
+ new BindParams(false, false, false, REDACTION_TYPE_NONE),
false /* forceInflate */,
null /* callback */);
Assert.assertNull(mRow.getEntry().getRunningTask());
@@ -287,7 +298,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(),
R.layout.custom_view_dark));
RemoteViews decoratedMediaView = mBuilder.createContentView();
- Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!",
+ assertFalse("The decorated media style doesn't allow a view to be reapplied!",
NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
}
@@ -385,7 +396,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mRow.getPrivateLayout().removeAllViews();
mRow.getEntry().getSbn().getNotification().contentView =
new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height);
- inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+ inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE,
+ mRow);
assertEquals(0, mRow.getPrivateLayout().getChildCount());
verify(mRow, times(0)).onNotificationUpdated();
}
@@ -395,12 +407,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
final PromotedNotificationContentModel content =
new PromotedNotificationContentModel.Builder("key").build();
- when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content);
+ mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
- verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any());
+ mPromotedNotificationContentExtractor.verifyZeroExtractCalls();
}
@Test
@@ -410,12 +421,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
throws Exception {
final PromotedNotificationContentModel content =
new PromotedNotificationContentModel.Builder("key").build();
- when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content);
+ mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
- verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ mPromotedNotificationContentExtractor.verifyOneExtractCall();
assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
}
@@ -425,12 +435,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
final PromotedNotificationContentModel content =
new PromotedNotificationContentModel.Builder("key").build();
- when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content);
+ mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
- verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ mPromotedNotificationContentExtractor.verifyOneExtractCall();
assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
}
@@ -439,36 +448,107 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
final PromotedNotificationContentModel content =
new PromotedNotificationContentModel.Builder("key").build();
- when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content);
+ mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
- verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ mPromotedNotificationContentExtractor.verifyOneExtractCall();
assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
}
@Test
@EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_null() throws Exception {
- when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null);
+ mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), null);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
- verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ mPromotedNotificationContentExtractor.verifyOneExtractCall();
assertNull(mRow.getEntry().getPromotedNotificationContentModel());
}
+ @Test
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ public void testSensitiveContentPublicView_messageStyle() throws Exception {
+ String displayName = "Display Name";
+ String messageText = "Message Text";
+ String contentText = "Content Text";
+ Icon personIcon = Icon.createWithResource(mContext,
+ com.android.systemui.res.R.drawable.ic_person);
+ Person testPerson = new Person.Builder()
+ .setName(displayName)
+ .setIcon(personIcon)
+ .build();
+ Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle(testPerson);
+ messagingStyle.addMessage(new Notification.MessagingStyle.Message(messageText,
+ System.currentTimeMillis(), testPerson));
+ messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL);
+ messagingStyle.setShortcutIcon(personIcon);
+ Notification messageNotif = new Notification.Builder(mContext).setSmallIcon(
+ com.android.systemui.res.R.drawable.ic_person).setStyle(messagingStyle).build();
+ ExpandableNotificationRow row = mHelper.createRow(messageNotif);
+ inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_SENSITIVE_CONTENT, row);
+ NotificationContentView publicView = row.getPublicLayout();
+ assertNotNull(publicView);
+ // The display name should be included, but not the content or message text
+ assertFalse(hasText(publicView, messageText));
+ assertFalse(hasText(publicView, contentText));
+ assertTrue(hasText(publicView, displayName));
+ }
+
+ @Test
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ public void testSensitiveContentPublicView_nonMessageStyle() throws Exception {
+ String contentTitle = "Content Title";
+ String contentText = "Content Text";
+ Notification notif = new Notification.Builder(mContext).setSmallIcon(
+ com.android.systemui.res.R.drawable.ic_person)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .build();
+ ExpandableNotificationRow row = mHelper.createRow(notif);
+ inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_SENSITIVE_CONTENT, row);
+ NotificationContentView publicView = row.getPublicLayout();
+ assertNotNull(publicView);
+ assertFalse(hasText(publicView, contentText));
+ assertTrue(hasText(publicView, contentTitle));
+
+ // The standard public view should not use the content title or text
+ inflateAndWait(false, mNotificationInflater, FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_PUBLIC, row);
+ publicView = row.getPublicLayout();
+ assertFalse(hasText(publicView, contentText));
+ assertFalse(hasText(publicView, contentTitle));
+ }
+
+ private static boolean hasText(ViewGroup parent, CharSequence text) {
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ View child = parent.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ if (hasText((ViewGroup) child, text)) {
+ return true;
+ }
+ } else if (child instanceof TextView) {
+ return ((TextView) child).getText().toString().contains(text);
+ }
+ }
+ return false;
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
throws Exception {
- inflateAndWait(false /* expectingException */, inflater, contentToInflate, row);
+ inflateAndWait(false /* expectingException */, inflater, contentToInflate,
+ REDACTION_TYPE_NONE, row);
}
private static void inflateAndWait(boolean expectingException,
NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
+ @RedactionType int redactionType,
ExpandableNotificationRow row) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
final ExceptionHolder exceptionHolder = new ExceptionHolder();
@@ -496,7 +576,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
row.getEntry(),
row,
contentToInflate,
- new BindParams(),
+ new BindParams(false, false, false, redactionType),
false /* forceInflate */,
callback /* callback */);
assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 18517998096a..f25ba2c93c65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row
import android.app.Notification
import android.app.Person
import android.content.Context
+import android.graphics.drawable.Icon
import android.os.AsyncTask
import android.os.Build
import android.os.CancellationSignal
@@ -34,10 +35,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
@@ -45,6 +50,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
@@ -67,7 +73,6 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -110,7 +115,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
return inflatedSmartReplyState
}
}
- private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock()
+ private val promotedNotificationContentExtractor = FakePromotedNotificationContentExtractor()
+ private val conversationNotificationProcessor: ConversationNotificationProcessor = mock()
@Before
fun setUp() {
@@ -127,7 +133,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
NotificationRowContentBinderImpl(
cache,
mock(),
- mock<ConversationNotificationProcessor>(),
+ conversationNotificationProcessor,
mock(),
smartReplyStateInflater,
layoutInflaterFactoryProvider,
@@ -139,14 +145,14 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testIncreasedHeadsUpBeingUsed() {
- val params = BindParams()
- params.usesIncreasedHeadsUpHeight = true
+ val params =
+ BindParams(false, false, /* usesIncreasedHeadsUpHeight */ true, REDACTION_TYPE_NONE)
val builder = spy(builder)
notificationInflater.inflateNotificationViews(
row.entry,
row,
params,
- true /* inflateSynchronously */,
+ true, /* inflateSynchronously */
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
@@ -158,14 +164,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testIncreasedHeightBeingUsed() {
- val params = BindParams()
- params.usesIncreasedHeight = true
+ val params = BindParams(false, /* usesIncreasedHeight */ true, false, REDACTION_TYPE_NONE)
val builder = spy(builder)
notificationInflater.inflateNotificationViews(
row.entry,
row,
params,
- true /* inflateSynchronously */,
+ true, /* inflateSynchronously */
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
@@ -194,15 +199,18 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.entry.sbn.notification.contentView =
RemoteViews(mContext.packageName, R.layout.status_bar)
inflateAndWait(
- true /* expectingException */,
+ true, /* expectingException */
notificationInflater,
FLAG_CONTENT_VIEW_ALL,
+ REDACTION_TYPE_NONE,
row,
)
Assert.assertTrue(row.privateLayout.childCount == 0)
verify(row, times(0)).onNotificationUpdated()
}
+ @Test fun testInflationOfSensitiveContentPublicView() {}
+
@Test
fun testAsyncTaskRemoved() {
row.entry.abortTask()
@@ -218,8 +226,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.entry,
row,
FLAG_CONTENT_VIEW_ALL,
- BindParams(),
- false /* forceInflate */,
+ BindParams(false, false, false, REDACTION_TYPE_NONE),
+ false, /* forceInflate */
null, /* callback */
)
Assert.assertNull(row.entry.runningTask)
@@ -234,7 +242,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
packageContext = mContext,
remoteViews = NewRemoteViews(),
contentModel = NotificationContentModel(headsUpStatusBarModel),
- extractedPromotedNotificationContentModel = null,
+ promotedContent = null,
)
val countDownLatch = CountDownLatch(1)
NotificationRowContentBinderImpl.applyRemoteView(
@@ -432,7 +440,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
mContext.packageName,
com.android.systemui.tests.R.layout.invalid_notification_height,
)
- inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+ inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, REDACTION_TYPE_NONE, row)
Assert.assertEquals(0, row.privateLayout.childCount.toLong())
verify(row, times(0)).onNotificationUpdated()
}
@@ -441,7 +449,13 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testInflatePublicSingleLineView() {
row.publicLayout.removeAllViews()
- inflateAndWait(false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, row)
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ REDACTION_TYPE_NONE,
+ row,
+ )
Assert.assertNotNull(row.publicLayout.mSingleLineView)
Assert.assertTrue(row.publicLayout.mSingleLineView is HybridNotificationView)
}
@@ -449,12 +463,15 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
fun testInflatePublicSingleLineConversationView() {
val testPerson = Person.Builder().setName("Person").build()
+ val style = Notification.MessagingStyle(testPerson)
val messagingBuilder =
Notification.Builder(mContext, "no-id")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text")
- .setStyle(Notification.MessagingStyle(testPerson))
+ .setStyle(style)
+ whenever(conversationNotificationProcessor.processNotification(any(), any(), any()))
+ .thenReturn(style)
val messagingRow = spy(testHelper.createRow(messagingBuilder.build()))
messagingRow.publicLayout.removeAllViews()
@@ -462,6 +479,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
false,
notificationInflater,
FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ REDACTION_TYPE_NONE,
messagingRow,
)
Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView)
@@ -475,12 +493,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
val content = PromotedNotificationContentModel.Builder("key").build()
- whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content)
+ promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
- verify(promotedNotificationContentExtractor, never()).extractContent(any(), any())
+ promotedNotificationContentExtractor.verifyZeroExtractCalls()
}
@Test
@@ -488,12 +505,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
val content = PromotedNotificationContentModel.Builder("key").build()
- whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content)
+ promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
- verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ promotedNotificationContentExtractor.verifyOneExtractCall()
Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
}
@@ -502,12 +518,11 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
val content = PromotedNotificationContentModel.Builder("key").build()
- whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content)
+ promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
- verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ promotedNotificationContentExtractor.verifyOneExtractCall()
Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
}
@@ -515,15 +530,99 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenBothFlagsEnabled() {
val content = PromotedNotificationContentModel.Builder("key").build()
- whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
- .thenReturn(content)
+ promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
- verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ promotedNotificationContentExtractor.verifyOneExtractCall()
Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
}
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun testExtractsPromotedContent_null() {
+ promotedNotificationContentExtractor.resetForEntry(row.entry, null)
+
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+ promotedNotificationContentExtractor.verifyOneExtractCall()
+ Assert.assertNull(row.entry.promotedNotificationContentModel)
+ }
+
+ @Test
+ @Throws(java.lang.Exception::class)
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ fun testSensitiveContentPublicView_messageStyle() {
+ val displayName = "Display Name"
+ val messageText = "Message Text"
+ val contentText = "Content Text"
+ val personIcon = Icon.createWithResource(mContext, R.drawable.ic_person)
+ val testPerson = Person.Builder().setName(displayName).setIcon(personIcon).build()
+ val messagingStyle = Notification.MessagingStyle(testPerson)
+ messagingStyle.addMessage(
+ Notification.MessagingStyle.Message(messageText, System.currentTimeMillis(), testPerson)
+ )
+ messagingStyle.setConversationType(Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL)
+ messagingStyle.setShortcutIcon(personIcon)
+ val messageNotif =
+ Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(messagingStyle)
+ .build()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(messageNotif)
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_SENSITIVE_CONTENT,
+ newRow,
+ )
+ // The display name should be included, but not the content or message text
+ val publicView = newRow.publicLayout
+ Assert.assertNotNull(publicView)
+ Assert.assertFalse(hasText(publicView, messageText))
+ Assert.assertFalse(hasText(publicView, contentText))
+ Assert.assertTrue(hasText(publicView, displayName))
+ }
+
+ @Test
+ @Throws(java.lang.Exception::class)
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ fun testSensitiveContentPublicView_nonMessageStyle() {
+ val contentTitle = "Content Title"
+ val contentText = "Content Text"
+ val notif =
+ Notification.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .build()
+ val newRow: ExpandableNotificationRow = testHelper.createRow(notif)
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_SENSITIVE_CONTENT,
+ newRow,
+ )
+ var publicView = newRow.publicLayout
+ Assert.assertNotNull(publicView)
+ Assert.assertFalse(hasText(publicView, contentText))
+ Assert.assertTrue(hasText(publicView, contentTitle))
+
+ // The standard public view should not use the content title or text
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_PUBLIC,
+ REDACTION_TYPE_PUBLIC,
+ newRow,
+ )
+ publicView = newRow.publicLayout
+ Assert.assertFalse(hasText(publicView, contentText))
+ Assert.assertFalse(hasText(publicView, contentTitle))
+ }
+
private class ExceptionHolder {
var exception: Exception? = null
}
@@ -562,13 +661,20 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@InflationFlag contentToInflate: Int,
row: ExpandableNotificationRow,
) {
- inflateAndWait(false /* expectingException */, inflater, contentToInflate, row)
+ inflateAndWait(
+ false /* expectingException */,
+ inflater,
+ contentToInflate,
+ REDACTION_TYPE_NONE,
+ row,
+ )
}
private fun inflateAndWait(
expectingException: Boolean,
inflater: NotificationRowContentBinderImpl,
@InflationFlag contentToInflate: Int,
+ @RedactionType redactionType: Int,
row: ExpandableNotificationRow,
) {
val countDownLatch = CountDownLatch(1)
@@ -597,12 +703,26 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.entry,
row,
contentToInflate,
- BindParams(),
- false /* forceInflate */,
+ BindParams(false, false, false, redactionType),
+ false, /* forceInflate */
callback, /* callback */
)
Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS))
exceptionHolder.exception?.let { throw it }
}
+
+ fun hasText(parent: ViewGroup, text: CharSequence): Boolean {
+ for (i in 0 until parent.childCount) {
+ val child = parent.getChildAt(i)
+ if (child is ViewGroup) {
+ if (hasText(child, text)) {
+ return true
+ }
+ } else if (child is TextView) {
+ return child.text.toString().contains(text)
+ }
+ }
+ return false
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b323ef85b370..b8d18757afbb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -81,7 +81,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -201,7 +201,7 @@ public class NotificationTestHelper {
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
mock(HeadsUpStyleProvider.class),
- mock(PromotedNotificationContentExtractor.class),
+ new FakePromotedNotificationContentExtractor(),
mock(NotificationRowContentBinderLogger.class))
: new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
@@ -212,7 +212,7 @@ public class NotificationTestHelper {
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
mock(HeadsUpStyleProvider.class),
- mock(PromotedNotificationContentExtractor.class),
+ new FakePromotedNotificationContentExtractor(),
mock(NotificationRowContentBinderLogger.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index de40abb4d9d8..c6cffa9da13b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -952,11 +952,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Test
@EnableSceneContainer
public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() {
- boolean touchHandled = stopExpandingNotification();
+ stopExpandingNotification();
- verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding();
+ verify(mExpandHelper).finishExpanding();
verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any());
- assertTrue(touchHandled);
}
@Test
@@ -964,11 +963,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() {
stopExpandingNotification();
- verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding();
+ verify(mExpandHelper, never()).finishExpanding();
verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any());
}
- private boolean stopExpandingNotification() {
+ private void stopExpandingNotification() {
when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper);
when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true);
when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true);
@@ -983,13 +982,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
NotificationStackScrollLayoutController.TouchHandler touchHandler =
mController.getTouchHandler();
- return touchHandler.onTouchEvent(MotionEvent.obtain(
- /* downTime= */ 0,
- /* eventTime= */ 0,
- MotionEvent.ACTION_DOWN,
- 0,
- 0,
- /* metaState= */ 0
+ touchHandler.onTouchEvent(MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ MotionEvent.ACTION_DOWN,
+ 0,
+ 0,
+ /* metaState= */ 0
));
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index f48fd3c998b1..6bdd86efa8c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -241,7 +241,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
shadeTestUtil.setSplitShade(true)
val horizontalPosition = checkNotNull(dimens).horizontalPosition
- assertIs<HorizontalPosition.FloatAtEnd>(horizontalPosition)
+ assertIs<HorizontalPosition.FloatAtStart>(horizontalPosition)
assertThat(horizontalPosition.width).isEqualTo(200)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
index 31fbcb984105..baea1a1d2dca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.kt
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone
import android.app.Notification
import android.app.Notification.Builder
+import android.app.PendingIntent
import android.app.StatusBarManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -27,34 +28,39 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.InitController
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.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
+import com.android.systemui.shade.notificationShadeWindowView
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.NotificationRemoteInputManager
-import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.lockscreenShadeTransitionController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.notificationAlertsInteractor
import com.android.systemui.statusbar.notification.dynamicPrivacyController
import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition
-import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.visualInterruptionDecisionProvider
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.notificationRemoteInputManager
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -62,7 +68,9 @@ import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -72,32 +80,34 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class StatusBarNotificationPresenterTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
+ private val kosmos: Kosmos =
+ testKosmos().apply {
+ whenever(notificationShadeWindowView.resources).thenReturn(mContext.resources)
+ whenever(notificationStackScrollLayoutController.view).thenReturn(mock())
+ whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true)
+ commandQueue = CommandQueue(mContext, FakeDisplayTracker(mContext))
+
+ // override this controller with a mock, otherwise it would start some animators which
+ // are not cleaned up after these tests
+ lockscreenShadeTransitionController = mock()
+ }
+ // initiated by argumentCaptors later in the setup step, based on the flag states
private var interruptSuppressor: NotificationInterruptSuppressor? = null
private var alertsDisabledCondition: VisualInterruptionCondition? = null
private var vrModeCondition: VisualInterruptionCondition? = null
private var needsRedactionFilter: VisualInterruptionFilter? = null
private var panelsDisabledCondition: VisualInterruptionCondition? = null
- private val commandQueue: CommandQueue = CommandQueue(mContext, FakeDisplayTracker(mContext))
- private val shadeController: ShadeController = mock()
- private val notificationAlertsInteractor: NotificationAlertsInteractor = mock()
- private val keyguardStateController: KeyguardStateController = mock()
+ private val commandQueue: CommandQueue = kosmos.commandQueue
+ private val keyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+ private val notificationAlertsInteractor = kosmos.notificationAlertsInteractor
+ private val visualInterruptionDecisionProvider = kosmos.visualInterruptionDecisionProvider
private lateinit var underTest: StatusBarNotificationPresenter
@Before
fun setup() {
- mDependency.injectTestDependency(StatusBarStateController::class.java, mock())
- mDependency.injectTestDependency(ShadeController::class.java, shadeController)
- mDependency.injectMockDependency(NotificationRemoteInputManager.Callback::class.java)
- mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
-
- whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true)
-
underTest = createPresenter()
if (VisualInterruptionRefactor.isEnabled) {
verifyAndCaptureSuppressors()
@@ -147,13 +157,12 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressHeadsUp_disabledStatusBar_refactorDisabled() {
commandQueue.disable(
- DEFAULT_DISPLAY,
- StatusBarManager.DISABLE_EXPAND,
- 0,
- false, /* animate */
+ /* displayId = */ DEFAULT_DISPLAY,
+ /* flags = */ StatusBarManager.DISABLE_EXPAND,
+ /* reason = */ 0,
+ /* animate = */ false,
)
TestableLooper.get(this).processAllMessages()
-
assertWithMessage("The panel should suppress heads up while disabled")
.that(interruptSuppressor!!.suppressAwakeHeadsUp(createNotificationEntry()))
.isTrue()
@@ -163,13 +172,12 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressHeadsUp_disabledStatusBar_refactorEnabled() {
commandQueue.disable(
- DEFAULT_DISPLAY,
- StatusBarManager.DISABLE_EXPAND,
- 0,
- false, /* animate */
+ /* displayId = */ DEFAULT_DISPLAY,
+ /* flags = */ StatusBarManager.DISABLE_EXPAND,
+ /* reason = */ 0,
+ /* animate = */ false,
)
TestableLooper.get(this).processAllMessages()
-
assertWithMessage("The panel should suppress heads up while disabled")
.that(panelsDisabledCondition!!.shouldSuppress())
.isTrue()
@@ -179,13 +187,12 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() {
commandQueue.disable(
- DEFAULT_DISPLAY,
- 0,
- StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
- false, /* animate */
+ /* displayId = */ DEFAULT_DISPLAY,
+ /* flags = */ 0,
+ /* reason = */ StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+ /* animate = */ false,
)
TestableLooper.get(this).processAllMessages()
-
assertWithMessage(
"The panel should suppress interruptions while notification shade disabled"
)
@@ -197,13 +204,12 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() {
commandQueue.disable(
- DEFAULT_DISPLAY,
- 0,
- StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
- false, /* animate */
+ /* displayId = */ DEFAULT_DISPLAY,
+ /* flags = */ 0,
+ /* reason = */ StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
+ /* animate = */ false,
)
TestableLooper.get(this).processAllMessages()
-
assertWithMessage(
"The panel should suppress interruptions while notification shade disabled"
)
@@ -225,7 +231,6 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() {
whenever(keyguardStateController.isShowing()).thenReturn(true)
whenever(keyguardStateController.isOccluded()).thenReturn(false)
-
assertThat(interruptSuppressor!!.suppressAwakeHeadsUp(createFsiNotificationEntry()))
.isFalse()
}
@@ -235,9 +240,7 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
fun testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() {
whenever(keyguardStateController.isShowing()).thenReturn(true)
whenever(keyguardStateController.isOccluded()).thenReturn(false)
-
assertThat(needsRedactionFilter!!.shouldSuppress(createFsiNotificationEntry())).isFalse()
-
val types: Set<VisualInterruptionType> = needsRedactionFilter!!.types
assertThat(types).contains(VisualInterruptionType.PEEK)
assertThat(types)
@@ -248,7 +251,6 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressInterruptions_vrMode_refactorDisabled() {
underTest.mVrMode = true
-
assertWithMessage("Vr mode should suppress interruptions")
.that(interruptSuppressor!!.suppressAwakeInterruptions(createNotificationEntry()))
.isTrue()
@@ -258,11 +260,9 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressInterruptions_vrMode_refactorEnabled() {
underTest.mVrMode = true
-
assertWithMessage("Vr mode should suppress interruptions")
.that(vrModeCondition!!.shouldSuppress())
.isTrue()
-
val types: Set<VisualInterruptionType> = vrModeCondition!!.types
assertThat(types).contains(VisualInterruptionType.PEEK)
assertThat(types).doesNotContain(VisualInterruptionType.PULSE)
@@ -273,7 +273,6 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@DisableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() {
whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false)
-
assertWithMessage("When alerts aren't enabled, interruptions are suppressed")
.that(interruptSuppressor!!.suppressInterruptions(createNotificationEntry()))
.isTrue()
@@ -283,55 +282,91 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
@EnableFlags(VisualInterruptionRefactor.FLAG_NAME)
fun testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() {
whenever(notificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false)
-
assertWithMessage("When alerts aren't enabled, interruptions are suppressed")
.that(alertsDisabledCondition!!.shouldSuppress())
.isTrue()
-
val types: Set<VisualInterruptionType> = alertsDisabledCondition!!.types
assertThat(types).contains(VisualInterruptionType.PEEK)
assertThat(types).contains(VisualInterruptionType.PULSE)
assertThat(types).contains(VisualInterruptionType.BUBBLE)
}
- private fun createPresenter(): StatusBarNotificationPresenter {
- val shadeViewController: ShadeViewController = mock()
+ @Test
+ @EnableSceneContainer
+ fun testExpandSensitiveNotification_onLockScreen_opensShade() =
+ kosmos.runTest {
+ // Given we are on the keyguard
+ kosmos.sysuiStatusBarStateController.state = StatusBarState.KEYGUARD
+ // And the device is locked
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
- val notificationShadeWindowView: NotificationShadeWindowView = mock()
- whenever(notificationShadeWindowView.resources).thenReturn(mContext.resources)
+ // When the user expands a sensitive Notification
+ val row = createRow()
+ val entry =
+ row.entry.apply { setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true) }
- val stackScrollLayoutController: NotificationStackScrollLayoutController = mock()
- whenever(stackScrollLayoutController.view).thenReturn(mock())
+ underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true)
- val initController: InitController = InitController()
+ // Then we open the locked shade
+ verify(kosmos.lockscreenShadeTransitionController)
+ // Explicit parameters to avoid issues with Kotlin default arguments in Mockito
+ .goToLockedShade(row, true)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun testExpandSensitiveNotification_onLockedShade_showsBouncer() =
+ kosmos.runTest {
+ // Given we are on the locked shade
+ kosmos.sysuiStatusBarStateController.state = StatusBarState.SHADE_LOCKED
+ // And the device is locked
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+
+ // When the user expands a sensitive Notification
+ val entry =
+ createRow().entry.apply {
+ setSensitive(/* sensitive= */ true, /* deviceSensitive= */ true)
+ }
+ underTest.onExpandClicked(entry, mock(), /* nowExpanded= */ true)
+
+ // Then we show the bouncer
+ verify(kosmos.activityStarter).dismissKeyguardThenExecute(any(), eq(null), eq(false))
+ }
+ private fun createPresenter(): StatusBarNotificationPresenter {
+ val initController: InitController = InitController()
return StatusBarNotificationPresenter(
- mContext,
- shadeViewController,
+ /* context = */ mContext,
+ /* panel = */ mock(),
kosmos.panelExpansionInteractor,
/* quickSettingsController = */ mock(),
kosmos.headsUpManager,
- notificationShadeWindowView,
+ kosmos.notificationShadeWindowView,
kosmos.activityStarter,
- stackScrollLayoutController,
+ kosmos.notificationStackScrollLayoutController,
kosmos.dozeScrimController,
kosmos.notificationShadeWindowController,
kosmos.dynamicPrivacyController,
- keyguardStateController,
- notificationAlertsInteractor,
+ kosmos.keyguardStateController,
+ kosmos.notificationAlertsInteractor,
kosmos.lockscreenShadeTransitionController,
kosmos.powerInteractor,
- commandQueue,
+ kosmos.commandQueue,
kosmos.notificationLockscreenUserManager,
kosmos.sysuiStatusBarStateController,
/* notifShadeEventSource = */ mock(),
/* notificationMediaManager = */ mock(),
/* notificationGutsManager = */ mock(),
- initController,
- visualInterruptionDecisionProvider,
+ /* initController = */ initController,
+ kosmos.visualInterruptionDecisionProvider,
kosmos.notificationRemoteInputManager,
/* remoteInputManagerCallback = */ mock(),
/* notificationListContainer = */ mock(),
+ kosmos.deviceUnlockedInteractor,
)
.also { initController.executePostInitTasks() }
}
@@ -341,6 +376,7 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
val conditionCaptor = argumentCaptor<VisualInterruptionCondition>()
verify(visualInterruptionDecisionProvider, times(3)).addCondition(conditionCaptor.capture())
+
val conditions: List<VisualInterruptionCondition> = conditionCaptor.allValues
alertsDisabledCondition = conditions[0]
vrModeCondition = conditions[1]
@@ -362,19 +398,27 @@ class StatusBarNotificationPresenterTest : SysuiTestCase() {
interruptSuppressor = suppressorCaptor.lastValue
}
- private fun createNotificationEntry(): NotificationEntry {
- return NotificationEntryBuilder()
+ private fun createRow(): ExpandableNotificationRow {
+ val row: ExpandableNotificationRow = mock()
+ val entry: NotificationEntry = createNotificationEntry()
+ whenever(row.entry).thenReturn(entry)
+ entry.row = row
+ return row
+ }
+
+ private fun createNotificationEntry(): NotificationEntry =
+ NotificationEntryBuilder()
.setPkg("a")
.setOpPkg("a")
.setTag("a")
.setNotification(Builder(mContext, "a").build())
.build()
- }
private fun createFsiNotificationEntry(): NotificationEntry {
val notification: Notification =
- Builder(mContext, "a").setFullScreenIntent(mock(), true).build()
-
+ Builder(mContext, "a")
+ .setFullScreenIntent(mock<PendingIntent>(), /* highPriority= */ true)
+ .build()
return NotificationEntryBuilder()
.setPkg("a")
.setOpPkg("a")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index b560c591af1e..1ee8005fb7ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -20,7 +20,9 @@ import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -31,7 +33,6 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
@@ -43,7 +44,7 @@ import org.mockito.Mockito.verify
@RunWithLooper(setAsMainLooper = true)
class SystemUIBottomSheetDialogTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val configurationController = mock<ConfigurationController>()
private val config = mock<Configuration>()
private val delegate = mock<DialogDelegate<Dialog>>()
@@ -67,21 +68,17 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() {
@Test
fun onStart_registersConfigCallback() {
- kosmos.testScope.runTest {
+ kosmos.runTest {
dialog.show()
- runCurrent()
-
verify(configurationController).addCallback(any())
}
}
@Test
fun onStop_unregisterConfigCallback() {
- kosmos.testScope.runTest {
+ kosmos.runTest {
dialog.show()
- runCurrent()
dialog.dismiss()
- runCurrent()
verify(configurationController).removeCallback(any())
}
@@ -89,14 +86,12 @@ class SystemUIBottomSheetDialogTest : SysuiTestCase() {
@Test
fun onConfigurationChanged_calledInDelegate() {
- kosmos.testScope.runTest {
+ kosmos.runTest {
dialog.show()
- runCurrent()
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
verify(configurationController).addCallback(capture(captor))
captor.value.onConfigChanged(config)
- runCurrent()
verify(delegate).onConfigurationChanged(any(), any())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index 06b3b57bd133..b2378d2c3aae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.runOnMainThreadAndWaitForIdleSync
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.systemUIDialogFactory
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
@@ -78,6 +79,7 @@ class ModesDialogDelegateTest : SysuiTestCase() {
{ kosmos.modesDialogViewModel },
mockDialogEventLogger,
kosmos.mainCoroutineContext,
+ kosmos.shadeDialogContextInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 7b52dd836b51..5cd0846ded7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -501,6 +501,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_changeLockWallpaper() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml
deleted file mode 100644
index 8ba5e4901a7a..000000000000
--- a/packages/SystemUI/res/color/slider_active_track_color.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" />
- <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" />
-</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml
deleted file mode 100644
index 7980f804a516..000000000000
--- a/packages/SystemUI/res/color/slider_inactive_track_color.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" />
- <item android:color="@androidprv:color/materialColorPrimary" />
-</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml
deleted file mode 100644
index 8a98902426f8..000000000000
--- a/packages/SystemUI/res/color/slider_thumb_color.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2024 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="false" />
- <item android:color="@androidprv:color/materialColorPrimary" />
-</selector>
diff --git a/packages/SystemUI/res/drawable/audio_bars_idle.xml b/packages/SystemUI/res/drawable/audio_bars_idle.xml
new file mode 100644
index 000000000000..92a24755fece
--- /dev/null
+++ b/packages/SystemUI/res/drawable/audio_bars_idle.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="168dp"
+ android:height="168dp"
+ android:viewportWidth="168"
+ android:viewportHeight="168">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateX="121.161"
+ android:translateY="83.911">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+ </group>
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="102.911"
+ android:translateY="83.911">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="139.661"
+ android:translateY="83.911">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-37.16 -5.87 C-33.94,-5.87 -31.32,-3.32 -31.2,-0.13 C-31.2,-0.06 -31.2,0.02 -31.2,0.09 C-31.2,0.16 -31.2,0.23 -31.2,0.29 C-31.31,3.49 -33.94,6.05 -37.16,6.05 C-40.39,6.05 -43.01,3.49 -43.12,0.29 C-43.12,0.23 -43.12,0.16 -43.12,0.09 C-43.12,0.01 -43.12,-0.07 -43.12,-0.15 C-42.99,-3.33 -40.37,-5.87 -37.16,-5.87c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
index dfefb9d166af..4b7be3512f53 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
@@ -27,18 +27,7 @@
<solid android:color="@android:color/transparent"/>
</shape>
</item>
- <item
- android:end="20dp"
- android:gravity="end|center_vertical">
- <vector
- android:width="@dimen/hearing_devices_preset_spinner_icon_size"
- android:height="@dimen/hearing_devices_preset_spinner_icon_size"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?androidprv:attr/colorControlNormal">
- <path
- android:fillColor="#FF000000"
- android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
- </vector>
- </item>
+ <item android:end="20dp"
+ android:gravity="end|center_vertical"
+ android:drawable="@drawable/ic_hearing_device_expand" />
</layer-list> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml b/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml
new file mode 100644
index 000000000000..fdfe7134a748
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_hearing_device_expand.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@androidprv:color/materialColorOnSurface">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
new file mode 100644
index 000000000000..fd409a5a8bb1
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_layout.xml
@@ -0,0 +1,67 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/ambient_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/ambient_volume_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:contentDescription="@string/hearing_devices_ambient_unmute"
+ android:src="@drawable/ic_ambient_volume"
+ android:tint="@androidprv:color/materialColorOnSurface" />
+ <TextView
+ android:id="@+id/ambient_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="10dp"
+ android:text="@string/hearing_devices_ambient_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textDirection="locale"
+ android:textSize="16sp"
+ android:gravity="center_vertical"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium" />
+ <ImageView
+ android:id="@+id/ambient_expand_icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="10dp"
+ android:contentDescription="@string/hearing_devices_ambient_expand_controls"
+ android:src="@drawable/ic_hearing_device_expand"
+ android:tint="@androidprv:color/materialColorOnSurface" />
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/ambient_control_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml b/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml
new file mode 100644
index 000000000000..44ada8943b12
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_device_ambient_volume_slider.xml
@@ -0,0 +1,46 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+ android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/ambient_volume_slider_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textDirection="locale"
+ android:textSize="14sp"
+ android:labelFor="@+id/ambient_volume_slider"
+ android:gravity="center_vertical" />
+ <com.google.android.material.slider.Slider
+ style="@style/SystemUI.Material3.Slider"
+ android:id="@+id/ambient_volume_slider"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:layout_gravity="center_vertical"
+ android:theme="@style/Theme.Material3.DayNight"
+ app:labelBehavior="gone" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index bf04a6f64d6a..949a6abb9b9d 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -85,13 +85,22 @@
android:longClickable="false"/>
</LinearLayout>
+ <com.android.systemui.accessibility.hearingaid.AmbientVolumeLayout
+ android:id="@+id/ambient_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/preset_layout"
+ android:layout_marginTop="@dimen/hearing_devices_layout_margin" />
+
<LinearLayout
android:id="@+id/tools_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/preset_layout"
+ app:layout_constraintTop_toBottomOf="@id/ambient_layout"
android:layout_marginTop="@dimen/hearing_devices_layout_margin"
android:orientation="vertical">
<TextView
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index a3bad8f012ac..5ccedeafcb59 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -56,8 +56,8 @@
android:layout_marginTop="@dimen/volume_dialog_components_spacing"
android:background="@drawable/ripple_drawable_20dp"
android:contentDescription="@string/accessibility_volume_settings"
+ android:scaleType="centerInside"
android:soundEffectsEnabled="false"
- android:src="@drawable/horizontal_ellipsis"
android:tint="@androidprv:color/materialColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 9ac456c17084..0acf4109bbb5 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,7 +14,6 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
@@ -24,11 +23,6 @@
android:layout_width="@dimen/volume_dialog_slider_width"
android:layout_height="@dimen/volume_dialog_slider_height"
android:layout_gravity="center"
- android:theme="@style/Theme.Material3.Light"
android:orientation="vertical"
- app:thumbHeight="52dp"
- app:trackCornerSize="12dp"
- app:trackHeight="40dp"
- app:trackStopIndicatorSize="6dp"
- app:trackInsideCornerSize="2dp" />
+ android:theme="@style/Theme.Material3.DayNight" />
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_in.json b/packages/SystemUI/res/raw/audio_bars_in.json
new file mode 100644
index 000000000000..c90a59c47d64
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_in.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":18,"w":168,"h":168,"nm":"audio_bars_in","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":17,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":5,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_out.json b/packages/SystemUI/res/raw/audio_bars_out.json
new file mode 100644
index 000000000000..5eab65e057ab
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_out.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":31,"w":168,"h":168,"nm":"audio_bars_out","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"t":30,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/audio_bars_playing.json b/packages/SystemUI/res/raw/audio_bars_playing.json
new file mode 100644
index 000000000000..6ee8e1915f36
--- /dev/null
+++ b/packages/SystemUI/res/raw/audio_bars_playing.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":121,"w":168,"h":168,"nm":"audio_bars_playing","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[120.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":38,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":70,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":102,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[-0.016,-14.1],[5.941,-8.358],[5.961,0],[5.958,8.516],[0,14.274],[-5.958,8.515],[-5.961,0],[-5.972,-8.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-22.725],[5.957,-16.983],[5.961,0],[5.958,17.391],[0,23.149],[-5.958,17.39],[-5.961,0],[-5.957,-16.998]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[102.5,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":32,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-19.1],[5.957,-13.358],[5.961,0],[5.958,13.641],[0,19.399],[-5.958,13.64],[-5.961,0],[-5.957,-13.373]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-38.225],[5.957,-32.483],[5.961,0],[5.958,32.016],[0,37.774],[-5.958,32.015],[-5.961,0],[-5.957,-32.498]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[65.75,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":59,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0},"t":91,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-15.85],[5.957,-10.108],[5.961,0],[5.958,10.516],[0,16.274],[-5.958,10.515],[-5.961,0],[-5.957,-10.123]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-25.1],[5.957,-19.358],[5.961,0],[5.958,19.516],[0,25.274],[-5.958,19.515],[-5.961,0],[-5.957,-19.373]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[84,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-9.225],[5.957,-3.483],[5.961,0],[5.958,3.766],[0,9.524],[-5.958,3.765],[-5.961,0],[-5.957,-3.498]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-18.6],[5.957,-12.858],[5.961,0],[5.958,13.141],[0,18.899],[-5.958,13.14],[-5.961,0],[-5.957,-12.873]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.25,84,0],"ix":2,"l":2},"a":{"a":0,"k":[-37.161,0.089,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-5.961],[5.957,-0.219],[5.961,0],[5.958,0.203],[0,5.961],[-5.958,0.203],[-5.961,0],[-5.957,-0.235]],"c":true}]},{"t":120,"s":[{"i":[[-3.214,0],[-0.115,-3.191],[0,-0.073],[0.002,-0.067],[3.224,0],[0.107,3.198],[0,0.068],[-0.003,0.078]],"o":[[3.219,0],[0.003,0.073],[0,0.068],[-0.107,3.198],[-3.224,0],[-0.002,-0.067],[0,-0.079],[0.123,-3.184]],"v":[[0,-13.475],[5.957,-7.733],[5.961,0],[5.958,6.766],[0,12.524],[-5.958,6.765],[-5.961,0],[-5.957,-7.748]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-37.161,0.089],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":121,"st":0,"bm":0}],"markers":[{"tm":60,"cm":"1","dr":0}]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c1eff5f629b3..a77f5e4629c1 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -108,6 +108,9 @@
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
+ <!-- Dark Theme colors for notification shade/scrim -->
+ <color name="shade_panel">@android:color/system_accent1_900</color>
+
<!-- Keyboard shortcut helper dialog -->
<color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b4383156dc71..ec24c3df36a8 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -19,10 +19,13 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The maximum number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">4</integer>
+ <!-- The number of columns in the Split Shade QuickSettings -->
+ <integer name="quick_settings_split_shade_num_columns">6</integer>
+
<!-- Use collapsed layout for media player in landscape QQS -->
<bool name="config_quickSettingsMediaLandscapeCollapsed">false</bool>
@@ -51,7 +54,9 @@
ignored. -->
<string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
<item>bottom_start:home</item>
- <item>bottom_end:create_note</item>
+ <!-- TODO(b/384119565): revisit decision on defaults -->
+ <item android:featureFlag="!com.android.systemui.glanceable_hub_v2_resources">bottom_end:create_note</item>
+ <item android:featureFlag="com.android.systemui.glanceable_hub_v2_resources">bottom_end:glanceable_hub</item>
</string-array>
<!-- Whether volume panel should use the large screen layout or not -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 28df2e2a1b8c..d2b7d0b90c43 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -31,6 +31,9 @@
<!-- The dark background color behind the shade -->
<color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color>
+ <!-- Colors for notification shade/scrim -->
+ <color name="shade_panel">@android:color/system_accent1_800</color>
+
<!-- The color of the background in the separated list of the Global Actions menu -->
<color name="global_actions_separated_background">#F5F5F5</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 56aaf4c0c564..c3d84ff39485 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1004,6 +1004,20 @@
<string name="hearing_devices_preset_label">Preset</string>
<!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
<string name="hearing_devices_spinner_item_selected">Selected</string>
+ <!-- QuickSettings: Title for ambient controls. [CHAR LIMIT=40]-->
+ <string name="hearing_devices_ambient_label">Surroundings</string>
+ <!-- QuickSettings: The text to show the control is for left side device. [CHAR LIMIT=30] -->
+ <string name="hearing_devices_ambient_control_left">Left</string>
+ <!-- QuickSettings: The text to show the control is for right side device. [CHAR LIMIT=30] -->
+ <string name="hearing_devices_ambient_control_right">Right</string>
+ <!-- QuickSettings: Content description for a button, that expands ambient volume sliders [CHAR_LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_expand_controls">Expand to left and right separated controls</string>
+ <!-- QuickSettings: Content description for a button, that collapses ambient volume sliders [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_collapse_controls">Collapse to unified control</string>
+ <!-- QuickSettings: Content description for a button, that mute ambient volume [CHAR_LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_mute">Mute surroundings</string>
+ <!-- QuickSettings: Content description for a button, that unmute ambient volume [CHAR LIMIT=NONE] -->
+ <string name="hearing_devices_ambient_unmute">Unmute surroundings</string>
<!-- QuickSettings: Title for related tools of hearing. [CHAR LIMIT=40]-->
<string name="hearing_devices_tools_label">Tools</string>
<!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
@@ -2504,14 +2518,14 @@
<!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_remove_tile_action">remove tile</string>
- <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] -->
- <string name="accessibility_qs_edit_tile_add_action">add tile to end</string>
+ <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to the last position" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_tile_add_action">add tile to the last position</string>
<!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_start_move">Move tile</string>
- <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] -->
- <string name="accessibility_qs_edit_tile_start_add">Add tile</string>
+ <!-- Accessibility action for context menu to add QS tile to a position [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_tile_start_add">Add tile to desired position</string>
<!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string>
@@ -2564,7 +2578,7 @@
<string name="accessibility_quick_settings_open_settings">Open <xliff:g name="page" example="Bluetooth">%s</xliff:g> settings.</string>
<!-- accessibility label for button to edit quick settings [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_edit">Edit order of settings.</string>
+ <string name="accessibility_quick_settings_edit">Edit order of Quick Settings.</string>
<!-- accessibility label for button to open power menu [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_power_menu">Power menu</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3156a50df96f..f6c1ecea2886 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -559,15 +559,18 @@
<style name="SystemUI.Material3.Slider.Volume">
<item name="trackHeight">40dp</item>
<item name="thumbHeight">52dp</item>
+ <item name="trackCornerSize">12dp</item>
+ <item name="trackInsideCornerSize">2dp</item>
+ <item name="trackStopIndicatorSize">6dp</item>
</style>
<style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
<item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
- <item name="thumbColor">@color/slider_thumb_color</item>
- <item name="tickColorActive">@color/slider_inactive_track_color</item>
- <item name="tickColorInactive">@color/slider_active_track_color</item>
- <item name="trackColorActive">@color/slider_active_track_color</item>
- <item name="trackColorInactive">@color/slider_inactive_track_color</item>
+ <item name="thumbColor">@androidprv:color/materialColorPrimary</item>
+ <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item>
+ <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item>
+ <item name="trackColorActive">@androidprv:color/materialColorPrimary</item>
+ <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item>
</style>
<style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
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 fc536bdb126b..6f13d637d5c5 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
@@ -20,6 +20,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static com.android.systemui.Flags.glanceableHubBackAction;
import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
import android.annotation.LongDef;
@@ -352,6 +353,10 @@ public class QuickStepContract {
}
// Disable back gesture on the hub, but not when the shade is showing.
if ((sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
+ // Allow back gesture on Glanceable Hub with back action support.
+ if (glanceableHubBackAction()) {
+ return false;
+ }
// Use QS expanded signal as the notification panel is always considered visible
// expanded when on the lock screen and when opening hub over lock screen. This does
// mean that back gesture is disabled when opening shade over hub while in portrait
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5af80cbd4b29..71b622aa0608 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -43,7 +43,6 @@ import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -85,8 +84,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/**
- * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
- * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ * Controller for a Clock provided by the registry and used on the keyguard. Functionality is forked
+ * from [AnimatableClockController].
*/
open class ClockEventController
@Inject
@@ -348,14 +347,6 @@ constructor(
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!MigrateClocksToBlueprint.isEnabled) {
- if (!isKeyguardVisible) {
- clock?.run {
- smallClock.animations.doze(if (isDozing) 1f else 0f)
- largeClock.animations.doze(if (isDozing) 1f else 0f)
- }
- }
- }
if (visible) {
refreshTime()
@@ -388,10 +379,6 @@ constructor(
}
private fun refreshTime() {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
clock?.smallClock?.events?.onTimeTick()
clock?.largeClock?.events?.onTimeTick()
}
@@ -483,14 +470,10 @@ constructor(
if (ModesUi.isEnabled) {
listenForDnd(this)
}
- if (MigrateClocksToBlueprint.isEnabled) {
- listenForDozeAmountTransition(this)
- listenForAnyStateToAodTransition(this)
- listenForAnyStateToLockscreenTransition(this)
- listenForAnyStateToDozingTransition(this)
- } else {
- listenForDozeAmount(this)
- }
+ listenForDozeAmountTransition(this)
+ listenForAnyStateToAodTransition(this)
+ listenForAnyStateToLockscreenTransition(this)
+ listenForAnyStateToDozingTransition(this)
}
}
smallTimeListener?.update(shouldTimeListenerRun)
@@ -596,11 +579,6 @@ constructor(
}
@VisibleForTesting
- internal fun listenForDozeAmount(scope: CoroutineScope): Job {
- return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
- }
-
- @VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
merge(
@@ -695,8 +673,7 @@ constructor(
isRunning = true
when (clockFace.config.tickRate) {
ClockTickRate.PER_MINUTE -> {
- // Handled by KeyguardClockSwitchController and
- // by KeyguardUpdateMonitorCallback#onTimeChanged.
+ // Handled by KeyguardUpdateMonitorCallback#onTimeChanged.
}
ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
ClockTickRate.PER_FRAME -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index df77a58c3b34..3f332f769c6e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -23,14 +23,11 @@ import android.graphics.Rect
import android.os.Bundle
import android.view.Display
import android.view.Gravity
-import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams
-import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
@@ -45,7 +42,6 @@ class ConnectedDisplayKeyguardPresentation
constructor(
@Assisted display: Display,
context: Context,
- private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
private val clockRegistry: ClockRegistry,
private val clockEventController: ClockEventController,
) :
@@ -53,12 +49,11 @@ constructor(
context,
display,
R.style.Theme_SystemUI_KeyguardPresentation,
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
) {
private lateinit var rootView: FrameLayout
private var clock: View? = null
- private lateinit var keyguardStatusViewController: KeyguardStatusViewController
private lateinit var faceController: ClockFaceController
private lateinit var clockFrame: FrameLayout
@@ -82,7 +77,7 @@ constructor(
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
clock?.let {
faceController.events.onTargetRegionChanged(
@@ -95,11 +90,7 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (MigrateClocksToBlueprint.isEnabled) {
- onCreateV2()
- } else {
- onCreate()
- }
+ onCreateV2()
}
fun onCreateV2() {
@@ -112,39 +103,15 @@ constructor(
setClock(clockRegistry.createCurrentClock())
}
- fun onCreate() {
- setContentView(
- LayoutInflater.from(context)
- .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
- )
-
- setFullscreen()
-
- clock = requireViewById(R.id.clock)
- keyguardStatusViewController =
- keyguardStatusViewComponentFactory
- .build(clock as KeyguardStatusView, display)
- .keyguardStatusViewController
- .apply {
- setDisplayedOnSecondaryDisplay()
- init()
- }
- }
-
override fun onAttachedToWindow() {
- if (MigrateClocksToBlueprint.isEnabled) {
- clockRegistry.registerClockChangeListener(clockChangedListener)
- clockEventController.registerListeners(clock!!)
-
- faceController.animations.enter()
- }
+ clockRegistry.registerClockChangeListener(clockChangedListener)
+ clockEventController.registerListeners(clock!!)
+ faceController.animations.enter()
}
override fun onDetachedFromWindow() {
- if (MigrateClocksToBlueprint.isEnabled) {
- clockEventController.unregisterListeners()
- clockRegistry.unregisterClockChangeListener(clockChangedListener)
- }
+ clockEventController.unregisterListeners()
+ clockRegistry.unregisterClockChangeListener(clockChangedListener)
super.onDetachedFromWindow()
}
@@ -166,7 +133,7 @@ constructor(
context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width),
WRAP_CONTENT,
Gravity.CENTER,
- )
+ ),
)
clockEventController.clock = clockController
@@ -190,8 +157,6 @@ constructor(
@AssistedFactory
interface Factory {
/** Creates a new [Presentation] for the given [display]. */
- fun create(
- display: Display,
- ): ConnectedDisplayKeyguardPresentation
+ fun create(display: Display): ConnectedDisplayKeyguardPresentation
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 1083136b570a..acfa08643b63 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -26,11 +26,9 @@ import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.os.Trace;
-import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
-import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.View;
import android.view.WindowManager;
@@ -58,6 +56,9 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Provider;
+/**
+ * Manages Keyguard Presentations for non-primary display(s).
+ */
@SysUISingleton
public class KeyguardDisplayManager {
protected static final String TAG = "KeyguardDisplayManager";
@@ -170,14 +171,17 @@ public class KeyguardDisplayManager {
}
return false;
}
- if (mKeyguardStateController.isOccluded()
- && mDeviceStateHelper.isConcurrentDisplayActive(display)) {
+
+ final boolean deviceStateOccludesKeyguard =
+ mDeviceStateHelper.isConcurrentDisplayActive(display)
+ || mDeviceStateHelper.isRearDisplayOuterDefaultActive(display);
+ if (mKeyguardStateController.isOccluded() && deviceStateOccludesKeyguard) {
if (DEBUG) {
// When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the
// Keyguard state becomes "occluded". In this case, we should not show the
// KeyguardPresentation, since the activity is presenting content onto the
// non-default display.
- Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent"
+ Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent or rear"
+ " display is active");
}
return false;
@@ -326,44 +330,45 @@ public class KeyguardDisplayManager {
public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
@Nullable
- private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
-
- // TODO(b/271317597): These device states should be defined in DeviceStateManager
- private final int mConcurrentState;
- private boolean mIsInConcurrentDisplayState;
+ private DeviceState mDeviceState;
@Inject
DeviceStateHelper(
- @ShadeDisplayAware Context context,
DeviceStateManager deviceStateManager,
@Main Executor mainExecutor) {
-
- final String rearDisplayPhysicalAddress = context.getResources().getString(
- com.android.internal.R.string.config_rearDisplayPhysicalAddress);
- if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) {
- mRearDisplayPhysicalAddress = null;
- } else {
- mRearDisplayPhysicalAddress = DisplayAddress
- .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress));
- }
-
- mConcurrentState = context.getResources().getInteger(
- com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay);
deviceStateManager.registerCallback(mainExecutor, this);
}
@Override
public void onDeviceStateChanged(@NonNull DeviceState state) {
- // When concurrent state ends, the display also turns off. This is enforced in various
- // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke
- // hide() since that will happen through the onDisplayRemoved callback.
- mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState;
+ // When dual display or rear display mode ends, the display also turns off. This is
+ // enforced in various ExtensionRearDisplayPresentationTest CTS tests. So, we don't need
+ // to invoke hide() since that will happen through the onDisplayRemoved callback.
+ mDeviceState = state;
+ }
+
+ /**
+ * @return true if the device is in Dual Display mode, and the specified display is the
+ * rear facing (outer) display.
+ */
+ boolean isConcurrentDisplayActive(@NonNull Display display) {
+ return mDeviceState != null
+ && mDeviceState.hasProperty(
+ DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT)
+ && (display.getFlags() & Display.FLAG_REAR) != 0;
}
- boolean isConcurrentDisplayActive(Display display) {
- return mIsInConcurrentDisplayState
- && mRearDisplayPhysicalAddress != null
- && mRearDisplayPhysicalAddress.equals(display.getAddress());
+ /**
+ * @return true if the device is the updated Rear Display mode, and the specified display is
+ * the inner display. See {@link DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT}.
+ * Note that in this state, the outer display is the default display, while the inner
+ * display is the "rear" display.
+ */
+ boolean isRearDisplayOuterDefaultActive(@NonNull Display display) {
+ return mDeviceState != null
+ && mDeviceState.hasProperty(
+ DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)
+ && (display.getFlags() & Display.FLAG_REAR) != 0;
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index 07bd813c2420..40a86dc3713e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -19,13 +19,12 @@ package com.android.keyguard
import android.content.Context
import android.view.View
import com.android.systemui.customization.R as customR
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -55,16 +54,17 @@ constructor(
var statusViewCentered = false
private val filterKeyguardAndSplitShadeOnly: () -> Boolean = {
- statusBarStateController.getState() == KEYGUARD && !statusViewCentered }
+ statusBarStateController.getState() == KEYGUARD && !statusViewCentered
+ }
private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD }
private val translateAnimator by lazy {
- val smartSpaceViews = if (MigrateClocksToBlueprint.isEnabled) {
- // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer]
- val scrollXTranslation = { view: View, translation: Float ->
- view.scrollX = -translation.toInt()
- }
+ // Use scrollX instead of translationX as translation is already set by [AodBurnInLayer]
+ val scrollXTranslation = { view: View, translation: Float ->
+ view.scrollX = -translation.toInt()
+ }
+ val smartSpaceViews =
setOf(
ViewIdToTranslate(
viewId = sharedR.id.date_smartspace_view,
@@ -83,18 +83,8 @@ constructor(
direction = START,
shouldBeAnimated = filterKeyguard,
translateFunc = scrollXTranslation,
- )
+ ),
)
- } else {
- setOf(ViewIdToTranslate(
- viewId = R.id.keyguard_status_area,
- direction = START,
- shouldBeAnimated = filterKeyguard,
- translateFunc = { view, value ->
- (view as? KeyguardStatusAreaView)?.translateXFromUnfold = value
- }
- ))
- }
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
@@ -102,39 +92,39 @@ constructor(
ViewIdToTranslate(
viewId = customR.id.lockscreen_clock_view_large,
direction = START,
- shouldBeAnimated = filterKeyguardAndSplitShadeOnly
+ shouldBeAnimated = filterKeyguardAndSplitShadeOnly,
),
ViewIdToTranslate(
viewId = customR.id.lockscreen_clock_view,
direction = START,
- shouldBeAnimated = filterKeyguard
+ shouldBeAnimated = filterKeyguard,
),
ViewIdToTranslate(
viewId = R.id.notification_stack_scroller,
direction = END,
- shouldBeAnimated = filterKeyguardAndSplitShadeOnly
- )
+ shouldBeAnimated = filterKeyguardAndSplitShadeOnly,
+ ),
) + smartSpaceViews,
- progressProvider = unfoldProgressProvider
+ progressProvider = unfoldProgressProvider,
)
}
private val shortcutButtonsAnimator by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
- setOf(
- ViewIdToTranslate(
- viewId = R.id.start_button,
- direction = START,
- shouldBeAnimated = filterKeyguard
+ setOf(
+ ViewIdToTranslate(
+ viewId = R.id.start_button,
+ direction = START,
+ shouldBeAnimated = filterKeyguard,
+ ),
+ ViewIdToTranslate(
+ viewId = R.id.end_button,
+ direction = END,
+ shouldBeAnimated = filterKeyguard,
+ ),
),
- ViewIdToTranslate(
- viewId = R.id.end_button,
- direction = END,
- shouldBeAnimated = filterKeyguard
- )
- ),
- progressProvider = unfoldProgressProvider
+ progressProvider = unfoldProgressProvider,
)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 0305b5e5ab63..e76f38c8c75c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -26,7 +26,6 @@ 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.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.clocks.ClockMessageBuffers;
import com.android.systemui.res.R;
@@ -70,7 +69,7 @@ public abstract class ClockRegistryModule {
context,
layoutInflater,
resources,
- MigrateClocksToBlueprint.isEnabled(),
+
com.android.systemui.Flags.clockReactiveVariants()
),
context.getString(R.string.lockscreen_clock_id_fallback),
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
new file mode 100644
index 000000000000..7c141c1b561e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeLayout.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hearingaid;
+
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
+import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
+import com.android.systemui.res.R;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.primitives.Ints;
+
+import java.util.Map;
+
+/**
+ * A view of ambient volume controls.
+ *
+ * <p> It consists of a header with an expand icon and volume sliders for unified control and
+ * separated control for devices in the same set. Toggle the expand icon will make the UI switch
+ * between unified and separated control.
+ */
+public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi {
+
+ @Nullable
+ private AmbientVolumeUiListener mListener;
+ private ImageView mExpandIcon;
+ private ImageView mVolumeIcon;
+ private boolean mExpandable = true;
+ private boolean mExpanded = false;
+ private boolean mMutable = false;
+ private boolean mMuted = false;
+ private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
+ private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+
+ private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
+ (slider, value) -> {
+ if (mListener != null) {
+ final int side = mSideToSliderMap.inverse().get(slider);
+ mListener.onSliderValueChange(side, value);
+ }
+ };
+
+ public AmbientVolumeLayout(@Nullable Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, /* defStyleAttr= */ 0);
+ }
+
+ public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
+ }
+
+ public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ inflate(context, R.layout.hearing_device_ambient_volume_layout, /* root= */ this);
+ init();
+ }
+
+ private void init() {
+ mVolumeIcon = requireViewById(R.id.ambient_volume_icon);
+ mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
+ mVolumeIcon.setOnClickListener(v -> {
+ if (!mMutable) {
+ return;
+ }
+ setMuted(!mMuted);
+ if (mListener != null) {
+ mListener.onAmbientVolumeIconClick();
+ }
+ });
+ updateVolumeIcon();
+
+ mExpandIcon = requireViewById(R.id.ambient_expand_icon);
+ mExpandIcon.setOnClickListener(v -> {
+ setExpanded(!mExpanded);
+ if (mListener != null) {
+ mListener.onExpandIconClick();
+ }
+ });
+ updateExpandIcon();
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ @Override
+ public void setExpandable(boolean expandable) {
+ mExpandable = expandable;
+ if (!mExpandable) {
+ setExpanded(false);
+ }
+ updateExpandIcon();
+ }
+
+ @Override
+ public boolean isExpandable() {
+ return mExpandable;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ if (!mExpandable && expanded) {
+ return;
+ }
+ mExpanded = expanded;
+ updateExpandIcon();
+ updateLayout();
+ }
+
+ @Override
+ public boolean isExpanded() {
+ return mExpanded;
+ }
+
+ @Override
+ public void setMutable(boolean mutable) {
+ mMutable = mutable;
+ if (!mMutable) {
+ mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+ setMuted(false);
+ }
+ updateVolumeIcon();
+ }
+
+ @Override
+ public boolean isMutable() {
+ return mMutable;
+ }
+
+ @Override
+ public void setMuted(boolean muted) {
+ if (!mMutable && muted) {
+ return;
+ }
+ mMuted = muted;
+ if (mMutable && mMuted) {
+ for (AmbientVolumeSlider slider : mSideToSliderMap.values()) {
+ slider.setValue(slider.getMin());
+ }
+ }
+ updateVolumeIcon();
+ }
+
+ @Override
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ @Override
+ public void setListener(@Nullable AmbientVolumeUiListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap) {
+ sideToDeviceMap.forEach((side, device) -> createSlider(side));
+ createSlider(SIDE_UNIFIED);
+
+ LinearLayout controlContainer = requireViewById(R.id.ambient_control_container);
+ controlContainer.removeAllViews();
+ if (!mSideToSliderMap.isEmpty()) {
+ for (int side : VALID_SIDES) {
+ final AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+ if (slider != null) {
+ controlContainer.addView(slider);
+ }
+ }
+ }
+ updateLayout();
+ }
+
+ @Override
+ public void setSliderEnabled(int side, boolean enabled) {
+ AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+ if (slider != null && slider.isEnabled() != enabled) {
+ slider.setEnabled(enabled);
+ updateLayout();
+ }
+ }
+
+ @Override
+ public void setSliderValue(int side, int value) {
+ AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+ if (slider != null && slider.getValue() != value) {
+ slider.setValue(value);
+ updateVolumeLevel();
+ }
+ }
+
+ @Override
+ public void setSliderRange(int side, int min, int max) {
+ AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+ if (slider != null) {
+ slider.setMin(min);
+ slider.setMax(max);
+ }
+ }
+
+ @Override
+ public void updateLayout() {
+ mSideToSliderMap.forEach((side, slider) -> {
+ if (side == SIDE_UNIFIED) {
+ slider.setVisibility(mExpanded ? GONE : VISIBLE);
+ } else {
+ slider.setVisibility(mExpanded ? VISIBLE : GONE);
+ }
+ if (!slider.isEnabled()) {
+ slider.setValue(slider.getMin());
+ }
+ });
+ updateVolumeLevel();
+ }
+
+ private void updateVolumeLevel() {
+ int leftLevel, rightLevel;
+ if (mExpanded) {
+ leftLevel = getVolumeLevel(SIDE_LEFT);
+ rightLevel = getVolumeLevel(SIDE_RIGHT);
+ } else {
+ final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
+ leftLevel = unifiedLevel;
+ rightLevel = unifiedLevel;
+ }
+ mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
+ AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
+ updateVolumeIcon();
+ }
+
+ private int getVolumeLevel(int side) {
+ AmbientVolumeSlider slider = mSideToSliderMap.get(side);
+ if (slider == null || !slider.isEnabled()) {
+ return 0;
+ }
+ return slider.getVolumeLevel();
+ }
+
+ private void updateExpandIcon() {
+ mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
+ mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
+ if (mExpandable) {
+ final int stringRes = mExpanded ? R.string.hearing_devices_ambient_collapse_controls
+ : R.string.hearing_devices_ambient_expand_controls;
+ mExpandIcon.setContentDescription(mContext.getString(stringRes));
+ } else {
+ mExpandIcon.setContentDescription(null);
+ }
+ }
+
+ private void updateVolumeIcon() {
+ mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
+ if (mMutable) {
+ final int stringRes = mMuted ? R.string.hearing_devices_ambient_unmute
+ : R.string.hearing_devices_ambient_mute;
+ mVolumeIcon.setContentDescription(mContext.getString(stringRes));
+ mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ } else {
+ mVolumeIcon.setContentDescription(null);
+ mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+ }
+
+ private void createSlider(int side) {
+ if (mSideToSliderMap.containsKey(side)) {
+ return;
+ }
+ AmbientVolumeSlider slider = new AmbientVolumeSlider(mContext);
+ slider.addOnChangeListener(mSliderOnChangeListener);
+ if (side == SIDE_LEFT) {
+ slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_left));
+ } else if (side == SIDE_RIGHT) {
+ slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_right));
+ }
+ mSideToSliderMap.put(side, slider);
+ }
+
+ @VisibleForTesting
+ ImageView getVolumeIcon() {
+ return mVolumeIcon;
+ }
+
+ @VisibleForTesting
+ ImageView getExpandIcon() {
+ return mExpandIcon;
+ }
+
+ @VisibleForTesting
+ Map<Integer, AmbientVolumeSlider> getSliders() {
+ return mSideToSliderMap;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
new file mode 100644
index 000000000000..92338ef3773c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/AmbientVolumeSlider.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.hearingaid;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.res.R;
+
+import com.google.android.material.slider.Slider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A view of ambient volume slider.
+ * <p> It consists by a title {@link TextView} with a volume control {@link Slider}.
+ */
+public class AmbientVolumeSlider extends LinearLayout {
+
+ private final TextView mTitle;
+ private final Slider mSlider;
+ private final List<OnChangeListener> mChangeListeners = new ArrayList<>();
+ private final Slider.OnSliderTouchListener mSliderTouchListener =
+ new Slider.OnSliderTouchListener() {
+ @Override
+ public void onStartTrackingTouch(@NonNull Slider slider) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(@NonNull Slider slider) {
+ final int value = Math.round(slider.getValue());
+ for (OnChangeListener listener : mChangeListeners) {
+ listener.onValueChange(AmbientVolumeSlider.this, value);
+ }
+ }
+ };
+ public AmbientVolumeSlider(@Nullable Context context) {
+ this(context, /* attrs= */ null);
+ }
+
+ public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, /* defStyleAttr= */ 0);
+ }
+
+ public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
+ }
+
+ public AmbientVolumeSlider(@Nullable Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ inflate(context, R.layout.hearing_device_ambient_volume_slider, /* root= */ this);
+ mTitle = requireViewById(R.id.ambient_volume_slider_title);
+ mSlider = requireViewById(R.id.ambient_volume_slider);
+ mSlider.addOnSliderTouchListener(mSliderTouchListener);
+ }
+
+ /**
+ * Sets title for the ambient volume slider.
+ * <p> If text is null or empty, then {@link TextView} is hidden.
+ */
+ public void setTitle(@Nullable String text) {
+ mTitle.setText(text);
+ mTitle.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
+ }
+
+ /** Gets title for the ambient volume slider. */
+ public CharSequence getTitle() {
+ return mTitle.getText();
+ }
+
+ /**
+ * Adds the callback to the ambient volume slider to get notified when the value is changed by
+ * user.
+ * <p> Note: The {@link OnChangeListener#onValueChange(AmbientVolumeSlider, int)} will be
+ * called when user's finger take off from the slider.
+ */
+ public void addOnChangeListener(@Nullable OnChangeListener listener) {
+ if (listener == null) {
+ return;
+ }
+ mChangeListeners.add(listener);
+ }
+
+ /** Sets max value to the ambient volume slider. */
+ public void setMax(float max) {
+ mSlider.setValueTo(max);
+ }
+
+ /** Gets max value from the ambient volume slider. */
+ public float getMax() {
+ return mSlider.getValueTo();
+ }
+
+ /** Sets min value to the ambient volume slider. */
+ public void setMin(float min) {
+ mSlider.setValueFrom(min);
+ }
+
+ /** Gets min value from the ambient volume slider. */
+ public float getMin() {
+ return mSlider.getValueFrom();
+ }
+
+ /** Sets value to the ambient volume slider. */
+ public void setValue(float value) {
+ mSlider.setValue(value);
+ }
+
+ /** Gets value from the ambient volume slider. */
+ public float getValue() {
+ return mSlider.getValue();
+ }
+
+ /** Sets the enable state to the ambient volume slider. */
+ public void setEnabled(boolean enabled) {
+ mSlider.setEnabled(enabled);
+ }
+
+ /** Gets the enable state of the ambient volume slider. */
+ public boolean isEnabled() {
+ return mSlider.isEnabled();
+ }
+
+ /**
+ * Gets the volume value of the ambient volume slider.
+ * <p> The volume level is divided into 5 levels:
+ * Level 0 corresponds to the minimum volume value. The range between the minimum and maximum
+ * volume is divided into 4 equal intervals, represented by levels 1 to 4.
+ */
+ public int getVolumeLevel() {
+ if (!mSlider.isEnabled()) {
+ return 0;
+ }
+ final double min = mSlider.getValueFrom();
+ final double max = mSlider.getValueTo();
+ final double levelGap = (max - min) / 4.0;
+ final double value = mSlider.getValue();
+ return (int) Math.ceil((value - min) / levelGap);
+ }
+
+ /** Interface definition for a callback invoked when a slider's value is changed. */
+ public interface OnChangeListener {
+ /** Called when the finger is take off from the slider. */
+ void onValueChange(@NonNull AmbientVolumeSlider slider, int value);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 56435df1ad2c..73aabc3cf95a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -52,10 +52,12 @@ import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.settingslib.bluetooth.AmbientVolumeUiController;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
@@ -108,7 +110,6 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
private SystemUIDialog mDialog;
- private RecyclerView mDeviceList;
private List<DeviceItem> mHearingDeviceItemList;
private HearingDevicesListAdapter mDeviceListAdapter;
@@ -134,6 +135,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
};
+ private AmbientVolumeUiController mAmbientController;
+
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -225,13 +228,17 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
refreshDeviceUi();
- if (mPresetController != null) {
- mPresetController.setDevice(getActiveHearingDevice());
- mMainHandler.post(() -> {
+ mMainHandler.post(() -> {
+ CachedBluetoothDevice device = getActiveHearingDevice();
+ if (mPresetController != null) {
+ mPresetController.setDevice(device);
mPresetLayout.setVisibility(
mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
- });
- }
+ }
+ if (mAmbientController != null) {
+ mAmbientController.loadDevice(device);
+ }
+ });
}
@Override
@@ -272,13 +279,13 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
- mDeviceList = dialog.requireViewById(R.id.device_list);
- mPresetLayout = dialog.requireViewById(R.id.preset_layout);
- mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
setupDeviceListView(dialog);
- setupPresetSpinner(dialog);
setupPairNewDeviceButton(dialog);
+ setupPresetSpinner(dialog);
+ if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
+ setupAmbientControls();
+ }
if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
setupRelatedToolsView(dialog);
}
@@ -286,41 +293,50 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
@Override
public void onStart(@NonNull SystemUIDialog dialog) {
- if (mLocalBluetoothManager == null) {
- return;
- }
- mLocalBluetoothManager.getEventManager().registerCallback(this);
- if (mPresetController != null) {
- mPresetController.registerHapCallback();
- }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+ }
+ if (mPresetController != null) {
+ mPresetController.registerHapCallback();
+ }
+ if (mAmbientController != null) {
+ mAmbientController.start();
+ }
+ });
}
@Override
public void onStop(@NonNull SystemUIDialog dialog) {
- if (mLocalBluetoothManager == null) {
- return;
- }
-
- if (mPresetController != null) {
- mPresetController.unregisterHapCallback();
- }
- mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+ ThreadUtils.postOnBackgroundThread(() -> {
+ if (mLocalBluetoothManager != null) {
+ mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+ }
+ if (mPresetController != null) {
+ mPresetController.unregisterHapCallback();
+ }
+ if (mAmbientController != null) {
+ mAmbientController.stop();
+ }
+ });
}
private void setupDeviceListView(SystemUIDialog dialog) {
- mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
+ final RecyclerView deviceList = dialog.requireViewById(R.id.device_list);
+ deviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
mHearingDeviceItemList = getHearingDeviceItemList();
mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
- mDeviceList.setAdapter(mDeviceListAdapter);
+ deviceList.setAdapter(mDeviceListAdapter);
}
private void setupPresetSpinner(SystemUIDialog dialog) {
mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
mPresetController.setDevice(getActiveHearingDevice());
+ mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
mPresetSpinner.setAdapter(mPresetInfoAdapter);
- // disable redundant Touch & Hold accessibility action for Switch Access
+ // Disable redundant Touch & Hold accessibility action for Switch Access
mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull View host,
@@ -349,12 +365,20 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
}
});
+ mPresetLayout = dialog.requireViewById(R.id.preset_layout);
mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
+ private void setupAmbientControls() {
+ final AmbientVolumeLayout ambientLayout = mDialog.requireViewById(R.id.ambient_layout);
+ mAmbientController = new AmbientVolumeUiController(
+ mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
+ mAmbientController.setShowUiWhenLocalDataExist(false);
+ mAmbientController.loadDevice(getActiveHearingDevice());
+ }
+
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
final Button pairButton = dialog.requireViewById(R.id.pair_new_device_button);
-
pairButton.setVisibility(mShowPairNewDevice ? VISIBLE : GONE);
if (mShowPairNewDevice) {
pairButton.setOnClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 232b62985ad0..47910f3d25bc 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -21,8 +21,11 @@ import android.window.OnBackAnimationCallback
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import android.window.WindowOnBackInvokedDispatcher
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.glanceableHubBackAction
import com.android.systemui.Flags.predictiveBackAnimateShade
+import com.android.systemui.communal.domain.interactor.CommunalBackActionInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -35,7 +38,6 @@ import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Handles requests to go back either from a button or gesture. */
@SysUISingleton
@@ -50,6 +52,7 @@ constructor(
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
private val shadeBackActionInteractor: ShadeBackActionInteractor,
private val qsController: QuickSettingsController,
+ private val communalBackActionInteractor: CommunalBackActionInteractor,
) : CoreStartable {
private var isCallbackRegistered = false
@@ -114,6 +117,12 @@ constructor(
if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
return true
}
+ if (glanceableHubBackAction()) {
+ if (communalBackActionInteractor.canBeDismissed()) {
+ communalBackActionInteractor.onBackPressed()
+ return true
+ }
+ }
if (shouldBackBeHandled()) {
if (shadeBackActionInteractor.canBeCollapsed()) {
// this is the Shade dismiss animation, so make sure QQS closes when it ends.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 4c2dc41fb759..d8c628fd680b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -155,6 +155,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
+ invalidate()
}
})
start()
@@ -191,6 +192,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
override fun onAnimationEnd(animation: Animator) {
drawDwell = false
resetDwellAlpha()
+ invalidate()
}
})
start()
@@ -248,6 +250,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
override fun onAnimationEnd(animation: Animator) {
drawDwell = false
+ invalidate()
}
})
start()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 9cfb5be478ed..b294dd1b0b71 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -41,6 +41,7 @@ import com.android.internal.R as InternalR
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.time.SystemClock
import dagger.assisted.Assisted
@@ -68,6 +69,7 @@ internal constructor(
private val uiEventLogger: UiEventLogger,
private val logger: BluetoothTileDialogLogger,
private val systemuiDialogFactory: SystemUIDialog.Factory,
+ private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
) : SystemUIDialog.Delegate {
private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
@@ -105,7 +107,7 @@ internal constructor(
}
override fun createDialog(): SystemUIDialog {
- return systemuiDialogFactory.create(this)
+ return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context)
}
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -405,10 +407,11 @@ internal constructor(
}
// updating icon colors
- val tintColor = context.getColor(
- if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
- else InternalR.color.materialColorOnSurface
- )
+ val tintColor =
+ context.getColor(
+ if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
+ else InternalR.color.materialColorOnSurface
+ )
// update icons
iconView.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index aef5f1f422d1..e6f02457d320 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -21,14 +21,17 @@ import android.graphics.drawable.Drawable
/**
* Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
- * [Icon.Resource] to a resource.
+ * [Icon.Resource] to a resource. In case of [Loaded], the resource ID [res] is optional.
*/
sealed class Icon {
abstract val contentDescription: ContentDescription?
- data class Loaded(
+ data class Loaded
+ @JvmOverloads
+ constructor(
val drawable: Drawable,
override val contentDescription: ContentDescription?,
+ @DrawableRes val res: Int? = null,
) : Icon()
data class Resource(
@@ -37,6 +40,11 @@ sealed class Icon {
) : Icon()
}
-/** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */
-fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon.Loaded =
- Icon.Loaded(this, contentDescription)
+/**
+ * Creates [Icon.Loaded] for a given drawable with an optional [contentDescription] and an optional
+ * [res].
+ */
+fun Drawable.asIcon(
+ contentDescription: ContentDescription? = null,
+ @DrawableRes res: Int? = null,
+): Icon.Loaded = Icon.Loaded(this, contentDescription, res)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 26abb48ce7db..73c0179cf8ec 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -55,6 +55,8 @@ interface CommunalSettingsRepository {
/** A [CommunalEnabledState] for the specified user. */
fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+ fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
@@ -138,6 +140,20 @@ constructor(
.flowOn(bgDispatcher)
}
+ override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
+ secureSettings
+ .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
+ // Force an update
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(
+ Settings.Secure.SCREENSAVER_ENABLED,
+ SCREENSAVER_ENABLED_SETTING_DEFAULT,
+ user.id,
+ ) == 1
+ }
+ .flowOn(bgDispatcher)
+
override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
broadcastDispatcher
.broadcastFlow(
@@ -182,6 +198,7 @@ constructor(
companion object {
const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
private const val ENABLED_SETTING_DEFAULT = 1
+ private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
new file mode 100644
index 000000000000..2ccf96abff79
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+
+/**
+ * {@link CommunalBackActionInteractor} is responsible for handling back gestures on the glanceable
+ * hub. When invoked SystemUI should navigate back to the lockscreen.
+ */
+@SysUISingleton
+class CommunalBackActionInteractor
+@Inject
+constructor(
+ private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val sceneInteractor: SceneInteractor,
+) {
+ fun canBeDismissed(): Boolean {
+ return communalInteractor.isCommunalShowing.value
+ }
+
+ fun onBackPressed() {
+ if (SceneContainerFlag.isEnabled) {
+ // TODO(b/384610333): Properly determine whether to go to dream or lockscreen on back.
+ sceneInteractor.changeScene(
+ toScene = Scenes.Lockscreen,
+ loggingReason = "CommunalBackActionInteractor",
+ )
+ } else {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ loggingReason = "CommunalBackActionInteractor",
+ )
+ }
+ }
+}
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 ea428698e476..947113da0e60 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
@@ -285,7 +285,7 @@ constructor(
* use [isIdleOnCommunal].
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
- val isCommunalShowing: Flow<Boolean> =
+ val isCommunalShowing: StateFlow<Boolean> =
flow { emit(SceneContainerFlag.isEnabled) }
.flatMapLatest { sceneContainerEnabled ->
if (sceneContainerEnabled) {
@@ -304,10 +304,10 @@ constructor(
columnName = "isCommunalShowing",
initialValue = false,
)
- .shareIn(
+ .stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- replay = 1,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 862b05bc9b5d..c1f21e4046a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -69,6 +69,12 @@ constructor(
// Start this eagerly since the value is accessed synchronously in many places.
.stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+ /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
+ val isScreensaverEnabled: Flow<Boolean> =
+ userInteractor.selectedUserInfo.flatMapLatest { user ->
+ repository.getScreensaverEnabledState(user)
+ }
+
/**
* Returns true if any glanceable hub functionality should be enabled via configs and flags.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
index 7d5b196dfaa8..c6f96e198b91 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt
@@ -18,10 +18,15 @@ package com.android.systemui.communal.ui.viewmodel
import android.annotation.SuppressLint
import android.app.DreamManager
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.kotlin.isDevicePluggedIn
+import com.android.systemui.util.kotlin.sample
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlin.coroutines.CoroutineContext
@@ -31,7 +36,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -41,6 +45,8 @@ class CommunalToDreamButtonViewModel
constructor(
@Background private val backgroundContext: CoroutineContext,
batteryController: BatteryController,
+ private val settingsInteractor: CommunalSettingsInteractor,
+ private val activityStarter: ActivityStarter,
private val dreamManager: DreamManager,
) : ExclusiveActivatable() {
@@ -49,11 +55,7 @@ constructor(
/** Whether we should show a button on hub to switch to dream. */
@SuppressLint("MissingPermission")
val shouldShowDreamButtonOnHub =
- batteryController
- .isDevicePluggedIn()
- .distinctUntilChanged()
- .map { isPluggedIn -> isPluggedIn && dreamManager.canStartDreaming(true) }
- .flowOn(backgroundContext)
+ batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext)
/** Handle a tap on the "show dream" button. */
fun onShowDreamButtonTap() {
@@ -63,9 +65,21 @@ constructor(
@SuppressLint("MissingPermission")
override suspend fun onActivated(): Nothing = coroutineScope {
launch {
- _requests.receiveAsFlow().collectLatest {
- withContext(backgroundContext) { dreamManager.startDream() }
- }
+ _requests
+ .receiveAsFlow()
+ .sample(settingsInteractor.isScreensaverEnabled)
+ .collectLatest { enabled ->
+ withContext(backgroundContext) {
+ if (enabled) {
+ dreamManager.startDream()
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(
+ Intent(Settings.ACTION_DREAM_SETTINGS),
+ 0,
+ )
+ }
+ }
+ }
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt
new file mode 100644
index 000000000000..31b6f0ff90fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface ComposeModule {
+ @Binds
+ @IntoMap
+ @ClassKey(ComposeTracingStartable::class)
+ fun composeTracing(impl: ComposeTracingStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt b/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt
new file mode 100644
index 000000000000..a015900d0817
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/compose/ComposeTracingStartable.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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(InternalComposeTracingApi::class)
+
+package com.android.systemui.compose
+
+import android.os.Trace
+import android.util.Log
+import androidx.compose.runtime.Composer
+import androidx.compose.runtime.CompositionTracer
+import androidx.compose.runtime.InternalComposeTracingApi
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "ComposeTracingStartable"
+private const val COMMAND_NAME = "composition-tracing"
+private const val SUBCOMMAND_ENABLE = "enable"
+private const val SUBCOMMAND_DISABLE = "disable"
+
+/**
+ * Sets up a [Command] to enable or disable Composition tracing.
+ *
+ * Usage:
+ * ```
+ * adb shell cmd statusbar composition-tracing [enable|disable]
+ * ${ANDROID_BUILD_TOP}/external/perfetto/tools/record_android_trace -c ${ANDROID_BUILD_TOP}/prebuilts/tools/linux-x86_64/perfetto/configs/trace_config_detailed.textproto
+ * ```
+ */
+@SysUISingleton
+class ComposeTracingStartable @Inject constructor(private val commandRegistry: CommandRegistry) :
+ CoreStartable {
+ @OptIn(InternalComposeTracingApi::class)
+ override fun start() {
+ Log.i(TAG, "Set up Compose tracing command")
+ commandRegistry.registerCommand(COMMAND_NAME) { CompositionTracingCommand() }
+ }
+}
+
+private class CompositionTracingCommand : ParseableCommand(COMMAND_NAME) {
+ val enable by subCommand(EnableCommand())
+ val disable by subCommand(DisableCommand())
+
+ override fun execute(pw: PrintWriter) {
+ if ((enable != null) xor (disable != null)) {
+ enable?.execute(pw)
+ disable?.execute(pw)
+ } else {
+ help(pw)
+ }
+ }
+}
+
+private class EnableCommand : ParseableCommand(SUBCOMMAND_ENABLE) {
+ override fun execute(pw: PrintWriter) {
+ val msg = "Enabled Composition tracing"
+ Log.i(TAG, msg)
+ pw.println(msg)
+ enableCompositionTracing()
+ }
+
+ private fun enableCompositionTracing() {
+ Composer.setTracer(
+ object : CompositionTracer {
+ override fun traceEventStart(key: Int, dirty1: Int, dirty2: Int, info: String) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, info)
+ }
+
+ override fun traceEventEnd() = Trace.traceEnd(Trace.TRACE_TAG_APP)
+
+ override fun isTraceInProgress(): Boolean = Trace.isEnabled()
+ }
+ )
+ }
+}
+
+private class DisableCommand : ParseableCommand(SUBCOMMAND_DISABLE) {
+ override fun execute(pw: PrintWriter) {
+ val msg = "Disabled Composition tracing"
+ Log.i(TAG, msg)
+ pw.println(msg)
+ disableCompositionTracing()
+ }
+
+ private fun disableCompositionTracing() {
+ Composer.setTracer(null)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 9ae106c3ab39..014c0db618e1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -267,6 +267,7 @@ public class FrameworkServicesModule {
}
@Provides
+ @Nullable
@Singleton
static VirtualDeviceManager provideVirtualDeviceManager(Context context) {
return context.getSystemService(VirtualDeviceManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index d6f8957ace33..7ebe52f3bd58 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,6 +54,7 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryMod
import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
+import com.android.systemui.compose.ComposeModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
@@ -133,6 +134,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
@@ -141,7 +143,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
import com.android.systemui.statusbar.phone.LetterboxModule;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.PolicyModule;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
@@ -214,6 +215,7 @@ import javax.inject.Named;
ClockRegistryModule.class,
CommunalModule.class,
CommonDataLayerModule.class,
+ ComposeModule.class,
ConfigurationModule.class,
ConfigurationRepositoryModule.class,
CommonUsageStatsDataLayerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 571b37f43fd4..b272d65a8a11 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -54,6 +54,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Flags;
import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
@@ -210,6 +211,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mCommunalVisible = communalVisible;
updateLifecycleStateLocked();
+ updateGestureBlockingLocked();
});
}
};
@@ -585,7 +587,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private void updateGestureBlockingLocked() {
final boolean shouldBlock = mStarted && !mShadeExpanded && !mBouncerShowing
- && !isDreamInPreviewMode();
+ && !isDreamInPreviewMode()
+ && !(Flags.glanceableHubBackAction() && mCommunalVisible);
if (shouldBlock) {
mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index f549e64ca853..d0065c8b06c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting
import androidx.core.math.MathUtils
import com.android.app.animation.Interpolators
import com.android.internal.R
-import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
import com.android.systemui.Flags.fasterUnlockTransition
import com.android.systemui.dagger.SysUISingleton
@@ -206,7 +205,7 @@ constructor(
fun onUnlockAnimationFinished() {}
}
- /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
+ /** The SmartSpace view on the lockscreen. */
var lockscreenSmartspace: View? = null
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 74ee052f12b9..57f06fbd3bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -21,13 +21,13 @@ import android.app.StatusBarManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
-import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeDisplayAware
import dagger.Lazy
@@ -65,7 +65,7 @@ constructor(
icon =
Icon.Resource(
R.drawable.ic_camera,
- ContentDescription.Resource(R.string.accessibility_camera_button)
+ ContentDescription.Resource(R.string.accessibility_camera_button),
)
)
} else {
@@ -88,7 +88,7 @@ constructor(
cameraGestureHelper
.get()
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true)
}
private suspend fun isLaunchable(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index e8d3bfac6361..1b8baf657948 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -210,16 +210,16 @@ constructor(
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
return if (ModesUi.isEnabled) {
if (!isAvailable.value) {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
} else {
val dnd = interactor.dndMode.value
if (dnd == null) {
Log.wtf(TAG, "Triggered DND but it's null!?")
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
if (dnd.isActive) {
interactor.deactivateMode(dnd)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
} else {
if (interactor.shouldAskForZenDuration(dnd)) {
// NOTE: The dialog handles turning on the mode itself.
@@ -229,16 +229,16 @@ constructor(
)
} else {
interactor.activateMode(dnd)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
}
}
} else {
when {
- !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ !oldIsAvailable -> KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
zenMode != ZEN_MODE_OFF -> {
controller.setZen(ZEN_MODE_OFF, null, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
settingsValue == ZEN_DURATION_PROMPT ->
@@ -249,12 +249,12 @@ constructor(
settingsValue == ZEN_DURATION_FOREVER -> {
controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
else -> {
controller.setZen(ZEN_MODE_IMPORTANT_INTERRUPTIONS, conditionUri, TAG)
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
index 480ef5e19d8e..e2642a0964c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -18,15 +18,14 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
-import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.FlashlightController
import javax.inject.Inject
@@ -50,9 +49,9 @@ constructor(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_flashlight_icon_on,
- ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label),
),
- ActivationState.Active
+ ActivationState.Active,
)
}
@@ -61,9 +60,9 @@ constructor(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
Icon.Resource(
R.drawable.qs_flashlight_icon_off,
- ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label),
),
- ActivationState.Inactive
+ ActivationState.Inactive,
)
}
@@ -92,14 +91,14 @@ constructor(
} else {
FlashlightState.OffAvailable.toLockScreenState()
},
- TAG
+ TAG,
)
}
override fun onFlashlightError() {
trySendWithFailureLogging(
FlashlightState.OffAvailable.toLockScreenState(),
- TAG
+ TAG,
)
}
@@ -114,7 +113,7 @@ constructor(
FlashlightState.OffAvailable.toLockScreenState()
}
},
- TAG
+ TAG,
)
}
}
@@ -130,7 +129,7 @@ constructor(
flashlightController.setFlashlight(
flashlightController.isAvailable && !flashlightController.isEnabled
)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
index d335a1806a6d..06da281648a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
@@ -111,7 +111,7 @@ constructor(
transitionKey = CommunalTransitionKeys.SimpleFade,
)
}
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 1cf6183fec6c..ade65c38ff3c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,10 +21,10 @@ import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
-import com.android.systemui.res.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.res.R
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -71,7 +71,7 @@ interface KeyguardQuickAffordanceConfig {
/** The picker shows the item for selecting this affordance as it normally would. */
data class Default(
/** Optional [Intent] to use to start an activity to configure this affordance. */
- val configureIntent: Intent? = null,
+ val configureIntent: Intent? = null
) : PickerScreenState()
/**
@@ -134,34 +134,39 @@ interface KeyguardQuickAffordanceConfig {
) : LockScreenState()
}
- sealed class OnTriggeredResult {
+ sealed class OnTriggeredResult() {
/**
* Returning this as a result from the [onTriggered] method means that the implementation
* has taken care of the action, the system will do nothing.
+ *
+ * @param[actionLaunched] Whether the implementation handled the action by launching a
+ * dialog or an activity.
*/
- object Handled : OnTriggeredResult()
+ data class Handled(val actionLaunched: Boolean) : OnTriggeredResult()
/**
* Returning this as a result from the [onTriggered] method means that the implementation
* has _not_ taken care of the action and the system should start an activity using the
* given [Intent].
*/
- data class StartActivity(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- ) : OnTriggeredResult()
+ data class StartActivity(val intent: Intent, val canShowWhileLocked: Boolean) :
+ OnTriggeredResult()
/**
* Returning this as a result from the [onTriggered] method means that the implementation
* has _not_ taken care of the action and the system should show a Dialog using the given
* [AlertDialog] and [Expandable].
*/
- data class ShowDialog(
- val dialog: AlertDialog,
- val expandable: Expandable?,
- ) : OnTriggeredResult()
+ data class ShowDialog(val dialog: AlertDialog, val expandable: Expandable?) :
+ OnTriggeredResult()
}
+ /**
+ * Models an [OnTriggeredResult] that did or did not launch a dialog or activity for a given
+ * config key.
+ */
+ data class LaunchingFromTriggeredResult(val launched: Boolean, val configKey: String)
+
companion object {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index 1358634a55f8..1c9bc9f39663 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.media.AudioManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.ContentDescription
@@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -118,7 +118,7 @@ constructor(
audioManager.ringerModeInternal = newRingerMode
}
}
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
@@ -140,11 +140,11 @@ constructor(
.getSharedPreferences(
MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
Context.MODE_PRIVATE,
- userTracker.userId
+ userTracker.userId,
)
.getInt(
LAST_NON_SILENT_RINGER_MODE_KEY,
- ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+ ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index eafa1cea59f3..cb7702e090d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -30,7 +30,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -72,21 +71,15 @@ constructor(
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
val hasCards =
getPaymentCards(response.walletCards)?.isNotEmpty() == true
- trySendWithFailureLogging(
- hasCards,
- TAG,
- )
+ trySendWithFailureLogging(hasCards, TAG)
}
override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
Log.e(
TAG,
- "Wallet card retrieval error, message: \"${error?.message}\""
- )
- trySendWithFailureLogging(
- null,
- TAG,
+ "Wallet card retrieval error, message: \"${error?.message}\"",
)
+ trySendWithFailureLogging(null, TAG)
}
}
@@ -94,7 +87,7 @@ constructor(
callback,
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE,
)
withContext(backgroundDispatcher) {
@@ -107,7 +100,7 @@ constructor(
walletController.unregisterWalletChangeObservers(
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_WALLET_APP_CHANGE,
)
}
}
@@ -117,11 +110,7 @@ constructor(
if (hasCards == null) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
} else {
- state(
- isWalletAvailable(),
- hasCards,
- walletController.walletClient.tileIcon,
- )
+ state(isWalletAvailable(), hasCards, walletController.walletClient.tileIcon)
}
flowOf(state)
}
@@ -135,28 +124,28 @@ constructor(
explanation =
context.getString(
R.string.wallet_quick_affordance_unavailable_install_the_app
- ),
+ )
)
queryCards().isEmpty() ->
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
explanation =
context.getString(
R.string.wallet_quick_affordance_unavailable_configure_the_app
- ),
+ )
)
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}
override fun onTriggered(
- expandable: Expandable?,
+ expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
walletController.startQuickAccessUiIntent(
activityStarter,
expandable?.activityTransitionController(),
/* hasCard= */ true,
)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true)
}
private suspend fun queryCards(): List<WalletCard> {
@@ -199,10 +188,8 @@ constructor(
Icon.Loaded(
drawable = tileIcon,
contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
- ),
+ ContentDescription.Resource(res = R.string.accessibility_wallet_button),
+ )
)
} else {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
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 ae55825c9842..9c2daf52c5df 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
@@ -29,7 +29,6 @@ import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.dock.DockManager
@@ -62,6 +61,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
@@ -101,6 +101,14 @@ constructor(
val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow()
/**
+ * Whether a [KeyguardQuickAffordanceConfig.OnTriggeredResult] indicated that the system
+ * launched an activity or showed a dialog.
+ */
+ private val _launchingFromTriggeredResult =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?>(null)
+ val launchingFromTriggeredResult = _launchingFromTriggeredResult.asStateFlow()
+
+ /**
* Whether the UI should use the long press gesture to activate quick affordances.
*
* If `false`, the UI goes back to using single taps.
@@ -187,18 +195,45 @@ constructor(
metricsLogger.logOnShortcutTriggered(slotId, configKey)
when (val result = config.onTriggered(expandable)) {
- is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> {
+ setLaunchingFromTriggeredResult(
+ KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(
+ launched = true,
+ configKey,
+ )
+ )
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
expandable = expandable,
)
- is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
- is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog ->
+ }
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> {
+ setLaunchingFromTriggeredResult(
+ KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(
+ result.actionLaunched,
+ configKey,
+ )
+ )
+ }
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> {
+ setLaunchingFromTriggeredResult(
+ KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(
+ launched = true,
+ configKey,
+ )
+ )
showDialog(result.dialog, result.expandable)
+ }
}
}
+ fun setLaunchingFromTriggeredResult(
+ launchingResult: KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?
+ ) {
+ _launchingFromTriggeredResult.value = launchingResult
+ }
+
/**
* Selects an affordance with the given ID on the slot with the given ID.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index aa44b6d46289..382436cf9397 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor.scenetransition
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
@@ -38,7 +39,6 @@ import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* This class listens to scene framework scene transitions and manages keyguard transition framework
@@ -111,7 +111,10 @@ constructor(
if (currentTransitionId == null) return
if (prevTransition !is ObservableTransitionState.Transition) return
- if (idle.currentScene == prevTransition.toContent) {
+ if (
+ idle.currentScene == prevTransition.toContent ||
+ idle.currentOverlays.contains(prevTransition.toContent)
+ ) {
finishCurrentTransition()
} else {
val targetState =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
index e7803c5e964c..a4a5ba691965 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
@@ -17,12 +17,23 @@
package com.android.systemui.keyguard.ui.binder
import android.os.VibrationEffect
+import com.android.systemui.Flags
import kotlin.time.Duration.Companion.milliseconds
object KeyguardBottomAreaVibrations {
- val ShakeAnimationDuration = 300.milliseconds
- const val ShakeAnimationCycles = 5f
+ val ShakeAnimationDuration =
+ if (Flags.msdlFeedback()) {
+ 285.milliseconds
+ } else {
+ 300.milliseconds
+ }
+ val ShakeAnimationCycles =
+ if (Flags.msdlFeedback()) {
+ 3f
+ } else {
+ 5f
+ }
private const val SmallVibrationScale = 0.3f
private const val BigVibrationScale = 0.6f
@@ -32,7 +43,7 @@ object KeyguardBottomAreaVibrations {
.apply {
val vibrationDelayMs =
(ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
- .toInt()
+ .toInt()
val vibrationCount = ShakeAnimationCycles.toInt() * 2
repeat(vibrationCount) {
@@ -47,29 +58,13 @@ object KeyguardBottomAreaVibrations {
val Activated =
VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_TICK,
- BigVibrationScale,
- 0,
- )
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- 0.1f,
- 0,
- )
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.1f, 0)
.compose()
val Deactivated =
VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_TICK,
- BigVibrationScale,
- 0,
- )
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
- 0.1f,
- 0,
- )
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, BigVibrationScale, 0)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.1f, 0)
.compose()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 8725cdd273df..8a2e3dd791c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.binder
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.graphics.drawable.Animatable2
+import android.os.VibrationEffect
import android.util.Size
import android.view.View
import android.view.ViewGroup
@@ -33,25 +34,27 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.doOnEnd
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/** This is only for a SINGLE Quick affordance */
@SysUISingleton
@@ -60,8 +63,9 @@ class KeyguardQuickAffordanceViewBinder
constructor(
private val falsingManager: FalsingManager?,
private val vibratorHelper: VibratorHelper?,
+ private val msdlPlayer: MSDLPlayer,
private val logger: KeyguardQuickAffordancesLogger,
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
+ private val hapticsViewModelFactory: KeyguardQuickAffordanceHapticViewModel.Factory,
) {
private val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
@@ -88,6 +92,12 @@ constructor(
): Binding {
val button = view as ImageView
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+ val hapticsViewModel =
+ if (Flags.msdlFeedback()) {
+ hapticsViewModelFactory.create(viewModel)
+ } else {
+ null
+ }
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -98,15 +108,12 @@ constructor(
viewModel = buttonModel,
messageDisplayer = messageDisplayer,
)
+ hapticsViewModel?.updateActivatedHistory(buttonModel.isActivated)
}
}
launch {
- updateButtonAlpha(
- view = button,
- viewModel = viewModel,
- alphaFlow = alpha,
- )
+ updateButtonAlpha(view = button, viewModel = viewModel, alphaFlow = alpha)
}
launch {
@@ -117,6 +124,32 @@ constructor(
}
}
}
+
+ if (Flags.msdlFeedback()) {
+ launch {
+ hapticsViewModel
+ ?.quickAffordanceHapticState
+ ?.filter {
+ it !=
+ KeyguardQuickAffordanceHapticViewModel.HapticState
+ .NO_HAPTICS
+ }
+ ?.collect { state ->
+ when (state) {
+ KeyguardQuickAffordanceHapticViewModel.HapticState
+ .TOGGLE_ON -> msdlPlayer.playToken(MSDLToken.SWITCH_ON)
+ KeyguardQuickAffordanceHapticViewModel.HapticState
+ .TOGGLE_OFF ->
+ msdlPlayer.playToken(MSDLToken.SWITCH_OFF)
+ KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH ->
+ msdlPlayer.playToken(MSDLToken.LONG_PRESS)
+ KeyguardQuickAffordanceHapticViewModel.HapticState
+ .NO_HAPTICS -> Unit
+ }
+ hapticsViewModel.resetLaunchingFromTriggeredResult()
+ }
+ }
+ }
}
}
@@ -178,7 +211,7 @@ constructor(
com.android.internal.R.color.materialColorOnPrimaryFixed
} else {
com.android.internal.R.color.materialColorOnSurface
- },
+ }
)
)
@@ -221,12 +254,7 @@ constructor(
.getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
.toFloat()
val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
- )
+ ObjectAnimator.ofFloat(view, "translationX", -amplitude / 2, amplitude / 2)
shakeAnimator.duration =
KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
shakeAnimator.interpolator =
@@ -234,11 +262,17 @@ constructor(
shakeAnimator.doOnEnd { view.translationX = 0f }
shakeAnimator.start()
- vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ vibratorHelper?.playFeedback(KeyguardBottomAreaVibrations.Shake, msdlPlayer)
logger.logQuickAffordanceTapped(viewModel.configKey)
}
view.onLongClickListener =
- OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
+ OnLongClickListener(
+ falsingManager,
+ viewModel,
+ vibratorHelper,
+ onTouchListener,
+ msdlPlayer,
+ )
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
}
@@ -268,7 +302,7 @@ constructor(
Size(
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ),
+ )
)
}
@@ -297,7 +331,8 @@ constructor(
private val falsingManager: FalsingManager?,
private val viewModel: KeyguardQuickAffordanceViewModel,
private val vibratorHelper: VibratorHelper?,
- private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
+ private val onTouchListener: KeyguardQuickAffordanceOnTouchListener,
+ private val msdlPlayer: MSDLPlayer,
) : View.OnLongClickListener {
override fun onLongClick(view: View): Boolean {
if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
@@ -312,12 +347,13 @@ constructor(
slotId = viewModel.slotId,
)
)
- vibratorHelper?.vibrate(
+ vibratorHelper?.playFeedback(
if (viewModel.isActivated) {
KeyguardBottomAreaVibrations.Activated
} else {
KeyguardBottomAreaVibrations.Deactivated
- }
+ },
+ msdlPlayer,
)
}
@@ -328,7 +364,15 @@ constructor(
override fun onLongClickUseDefaultHapticFeedback(view: View) = false
}
- private data class ConfigurationBasedDimensions(
- val buttonSizePx: Size,
- )
+ private data class ConfigurationBasedDimensions(val buttonSizePx: Size)
+}
+
+private fun VibratorHelper.playFeedback(effect: VibrationEffect, msdlPlayer: MSDLPlayer) {
+ if (!Flags.msdlFeedback()) {
+ vibrate(effect)
+ } else {
+ if (effect == KeyguardBottomAreaVibrations.Shake) {
+ msdlPlayer.playToken(MSDLToken.FAILURE)
+ }
+ }
}
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 a2ce4ec5ce9b..6d270b219c81 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
@@ -127,21 +127,18 @@ object KeyguardRootViewBinder {
if (Flags.nonTouchscreenDevicesBypassFalsing()) {
if (
event.action == MotionEvent.ACTION_DOWN &&
- event.buttonState == MotionEvent.BUTTON_PRIMARY &&
- !event.isTouchscreenSource()
+ event.buttonState == MotionEvent.BUTTON_PRIMARY &&
+ !event.isTouchscreenSource()
) {
consumed = true
} else if (
- event.action == MotionEvent.ACTION_UP &&
- !event.isTouchscreenSource()
+ event.action == MotionEvent.ACTION_UP && !event.isTouchscreenSource()
) {
statusBarKeyguardViewManager?.showBouncer(true)
consumed = true
}
}
- viewModel.setRootViewLastTapPosition(
- Point(event.x.toInt(), event.y.toInt())
- )
+ viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
}
consumed
}
@@ -172,7 +169,6 @@ object KeyguardRootViewBinder {
launch("$TAG#alpha") {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
- childViews[statusViewId]?.alpha = alpha
childViews[burnInLayerId]?.alpha = alpha
}
}
@@ -253,18 +249,6 @@ object KeyguardRootViewBinder {
}
launch {
- viewModel.burnInLayerAlpha.collect { alpha ->
- childViews[statusViewId]?.alpha = alpha
- }
- }
-
- launch {
- viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
- childViews[statusViewId]?.alpha = alpha
- }
- }
-
- launch {
viewModel.scale.collect { scaleViewModel ->
if (scaleViewModel.scaleClockOnly) {
// For clocks except weather clock, we have scale transition besides
@@ -553,7 +537,6 @@ object KeyguardRootViewBinder {
return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true
}
- private val statusViewId = R.id.keyguard_status_view
private val burnInLayerId = R.id.burn_in_layer
private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
private val largeClockId = customR.id.lockscreen_clock_view_large
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 090b65922d2d..6fb31c0e4191 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -48,19 +48,16 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isInvisible
-import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
-import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
-import com.android.systemui.coroutines.newTracingContext
+import com.android.systemui.customization.R as customR
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.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
@@ -80,7 +77,6 @@ import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.shared.clocks.DefaultClockController
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
@@ -91,18 +87,13 @@ import com.android.systemui.util.settings.SecureSettings
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.customization.R as customR
/** Renders the preview of the lock screen. */
class KeyguardPreviewRenderer
@@ -110,7 +101,6 @@ class KeyguardPreviewRenderer
@AssistedInject
constructor(
@Application private val context: Context,
- @Application applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Main private val mainHandler: Handler,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -157,8 +147,6 @@ constructor(
val surfacePackage: SurfaceControlViewHost.SurfacePackage
get() = checkNotNull(host.surfacePackage)
- private lateinit var largeClockHostView: FrameLayout
- private lateinit var smallClockHostView: FrameLayout
private var smartSpaceView: View? = null
private val disposables = DisposableHandles()
@@ -166,29 +154,18 @@ constructor(
private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
- private val coroutineScope: CoroutineScope
-
@Style.Type private var themeStyle: Int? = null
init {
- coroutineScope =
- CoroutineScope(
- applicationScope.coroutineContext +
- Job() +
- newTracingContext("KeyguardPreviewRenderer")
- )
- disposables += DisposableHandle { coroutineScope.cancel() }
clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
-
quickAffordancesCombinedViewModel.enablePreviewMode(
initiallySelectedSlotId =
- bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID)
- ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID)
+ ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
- if (MigrateClocksToBlueprint.isEnabled) {
- clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
- }
+
+ clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
runBlocking(mainDispatcher) {
host =
SurfaceControlViewHost(
@@ -348,6 +325,7 @@ constructor(
smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
}
+ @OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
val keyguardRootView = KeyguardRootView(previewContext, null)
rootView.addView(
@@ -358,34 +336,23 @@ constructor(
),
)
- setUpUdfps(
- previewContext,
- if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView,
- )
+ setUpUdfps(previewContext, keyguardRootView)
setupShortcuts(keyguardRootView)
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
- if (MigrateClocksToBlueprint.isEnabled) {
- KeyguardPreviewClockViewBinder.bind(
- keyguardRootView,
- clockViewModel,
- clockRegistry,
- ::updateClockAppearance,
- ClockPreviewConfig(
- previewContext,
- getPreviewShadeLayoutWide(display!!),
- SceneContainerFlag.isEnabled,
- ),
- )
- } else {
- KeyguardPreviewClockViewBinder.bind(
- largeClockHostView,
- smallClockHostView,
- clockViewModel,
- )
- }
+ KeyguardPreviewClockViewBinder.bind(
+ keyguardRootView,
+ clockViewModel,
+ clockRegistry,
+ ::updateClockAppearance,
+ ClockPreviewConfig(
+ previewContext,
+ getPreviewShadeLayoutWide(display!!),
+ SceneContainerFlag.isEnabled,
+ ),
+ )
}
setUpSmartspace(previewContext, rootView)
@@ -451,82 +418,22 @@ constructor(
.inflate(R.layout.udfps_keyguard_preview, parentView, false) as View
// Place the UDFPS view in the proper sensor location
- if (MigrateClocksToBlueprint.isEnabled) {
- val lockId = KeyguardPreviewClockViewBinder.lockId
- finger.id = lockId
- parentView.addView(finger)
- val cs = ConstraintSet()
- cs.clone(parentView as ConstraintLayout)
- cs.apply {
- constrainWidth(lockId, sensorBounds.width())
- constrainHeight(lockId, sensorBounds.height())
- connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top)
- connect(lockId, START, PARENT_ID, START, sensorBounds.left)
- }
- cs.applyTo(parentView)
- } else {
- val fingerprintLayoutParams =
- FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
- fingerprintLayoutParams.setMarginsRelative(
- sensorBounds.left,
- sensorBounds.top,
- sensorBounds.right,
- sensorBounds.bottom,
- )
- parentView.addView(finger, fingerprintLayoutParams)
+ val lockId = KeyguardPreviewClockViewBinder.lockId
+ finger.id = lockId
+ parentView.addView(finger)
+ val cs = ConstraintSet()
+ cs.clone(parentView as ConstraintLayout)
+ cs.apply {
+ constrainWidth(lockId, sensorBounds.width())
+ constrainHeight(lockId, sensorBounds.height())
+ connect(lockId, TOP, PARENT_ID, TOP, sensorBounds.top)
+ connect(lockId, START, PARENT_ID, START, sensorBounds.left)
}
+ cs.applyTo(parentView)
}
private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
val resources = parentView.resources
- if (!MigrateClocksToBlueprint.isEnabled) {
- largeClockHostView = FrameLayout(previewContext)
- largeClockHostView.layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- )
- largeClockHostView.isInvisible = true
- parentView.addView(largeClockHostView)
-
- smallClockHostView = FrameLayout(previewContext)
- val layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- resources.getDimensionPixelSize(customR.dimen.small_clock_height),
- )
- layoutParams.topMargin =
- SystemBarUtils.getStatusBarHeight(previewContext) +
- resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top)
- smallClockHostView.layoutParams = layoutParams
- smallClockHostView.setPaddingRelative(
- /* start = */ resources.getDimensionPixelSize(customR.dimen.clock_padding_start),
- /* top = */ 0,
- /* end = */ 0,
- /* bottom = */ 0,
- )
- smallClockHostView.clipChildren = false
- parentView.addView(smallClockHostView)
- smallClockHostView.isInvisible = true
- }
-
- // TODO (b/283465254): Move the listeners to KeyguardClockRepository
- if (!MigrateClocksToBlueprint.isEnabled) {
- val clockChangeListener =
- object : ClockRegistry.ClockChangeListener {
- override fun onCurrentClockChanged() {
- onClockChanged()
- }
- }
- clockRegistry.registerClockChangeListener(clockChangeListener)
- disposables += DisposableHandle {
- clockRegistry.unregisterClockChangeListener(clockChangeListener)
- }
-
- clockController.registerListeners(parentView)
- disposables += DisposableHandle { clockController.unregisterListeners() }
- }
-
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -544,38 +451,9 @@ constructor(
},
)
disposables += DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }
-
- if (!MigrateClocksToBlueprint.isEnabled) {
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- if (clockController.clock !is DefaultClockController) {
- clockController.clock
- ?.largeClock
- ?.events
- ?.onTargetRegionChanged(
- KeyguardClockSwitch.getLargeClockRegion(parentView)
- )
- clockController.clock
- ?.smallClock
- ?.events
- ?.onTargetRegionChanged(
- KeyguardClockSwitch.getSmallClockRegion(parentView)
- )
- }
- }
- parentView.addOnLayoutChangeListener(layoutChangeListener)
- disposables += DisposableHandle {
- parentView.removeOnLayoutChangeListener(layoutChangeListener)
- }
- }
-
- onClockChanged()
}
private suspend fun updateClockAppearance(clock: ClockController, resources: Resources) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- clockController.clock = clock
- }
val colors = wallpaperColors
if (clockRegistry.seedColor == null && colors != null) {
// Seed color null means users do not override any color on the clock. The default
@@ -601,9 +479,7 @@ constructor(
// In clock preview, we should have a seed color for clock
// before setting clock to clockEventController to avoid updateColor with seedColor == null
// So in update colors, it should already have the correct theme in clockFaceController
- if (MigrateClocksToBlueprint.isEnabled) {
- clockController.clock = clock
- }
+ clockController.clock = clock
// When set clock to clockController,it will reset fontsize based on context.resources
// We need to override it with overlaid resources
clock.largeClock.events.onFontSettingChanged(
@@ -611,19 +487,6 @@ constructor(
)
}
- private fun onClockChanged() {
- if (MigrateClocksToBlueprint.isEnabled) {
- return
- }
- coroutineScope.launch {
- val clock = clockRegistry.createCurrentClock()
- clockController.clock = clock
- updateClockAppearance(clock, context.resources)
- updateLargeClock(clock)
- updateSmallClock(clock)
- }
- }
-
private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) {
keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let {
indicatorView ->
@@ -657,34 +520,6 @@ constructor(
}
}
- private fun updateLargeClock(clock: ClockController) {
- if (MigrateClocksToBlueprint.isEnabled) {
- return
- }
- clock.largeClock.events.onTargetRegionChanged(
- KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
- )
- if (shouldHighlightSelectedAffordance) {
- clock.largeClock.view.alpha = DIM_ALPHA
- }
- largeClockHostView.removeAllViews()
- largeClockHostView.addView(clock.largeClock.view)
- }
-
- private fun updateSmallClock(clock: ClockController) {
- if (MigrateClocksToBlueprint.isEnabled) {
- return
- }
- clock.smallClock.events.onTargetRegionChanged(
- KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
- )
- if (shouldHighlightSelectedAffordance) {
- clock.smallClock.view.alpha = DIM_ALPHA
- }
- smallClockHostView.removeAllViews()
- smallClockHostView.addView(clock.smallClock.view)
- }
-
private fun getPreviewShadeLayoutWide(display: Display): Boolean {
return if (display.displayId == 0) {
shadeInteractor.isShadeLayoutWide.value
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 160380bb09bc..57fe15d4f52c 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
@@ -24,7 +24,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -50,9 +49,6 @@ constructor(
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
if (emptyView.parent != null) {
// As emptyView is lazy, it might be already attached.
(emptyView.parent as? ViewGroup)?.removeView(emptyView)
@@ -68,17 +64,10 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
clockViewModel.burnInLayer = burnInLayer
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
constraintSet.apply {
// The empty view should not occupy any space
constrainHeight(R.id.burn_in_layer_empty_view, 1)
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 70bf8bca55b9..738fb73a4918 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
@@ -32,7 +32,6 @@ import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -82,9 +81,6 @@ constructor(
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
disposableHandle?.dispose()
disposableHandle =
KeyguardClockViewBinder.bind(
@@ -99,20 +95,12 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
keyguardClockViewModel.currentClock.value?.let { clock ->
constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
}
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
disposableHandle?.dispose()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 3a791fd45528..4bfe5f0458c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -24,7 +24,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.keyguard.MigrateClocksToBlueprint
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.NotificationPanelView
@@ -54,32 +53,25 @@ constructor(
sharedNotificationContainerBinder,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
constraintSet.apply {
val bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
- if (MigrateClocksToBlueprint.isEnabled) {
- val useLargeScreenHeader =
- context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
- val marginTopLargeScreen =
- largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- connect(
- R.id.nssl_placeholder,
- TOP,
- R.id.smart_space_barrier_bottom,
- BOTTOM,
- bottomMargin +
- if (useLargeScreenHeader) {
- marginTopLargeScreen
- } else {
- 0
- }
- )
- } else {
- connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
- }
+ val useLargeScreenHeader =
+ context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
+ val marginTopLargeScreen =
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+ connect(
+ R.id.nssl_placeholder,
+ TOP,
+ R.id.smart_space_barrier_bottom,
+ BOTTOM,
+ bottomMargin +
+ if (useLargeScreenHeader) {
+ marginTopLargeScreen
+ } else {
+ 0
+ },
+ )
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/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 620cc13a0c3a..fc26d18fde6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -25,7 +25,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
@@ -62,9 +61,6 @@ constructor(
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
// This moves the existing NSSL view to a different parent, as the controller is a
// singleton and recreating it has other bad side effects.
// In the SceneContainer, this is done by the NotificationSection composable.
@@ -78,10 +74,6 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
disposableHandle?.dispose()
disposableHandle =
sharedNotificationContainerBinder.bind(
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 73e14b1524f3..cd038d799f42 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
@@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -70,7 +69,6 @@ constructor(
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout)
@@ -98,7 +96,6 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
disposableHandle?.dispose()
disposableHandle =
@@ -111,13 +108,11 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context)
val smartspaceHorizontalPadding =
KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context)
constraintSet.apply {
- // migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
@@ -128,7 +123,6 @@ constructor(
dateWeatherPaddingStart,
)
- // migrate addSmartspaceView from KeyguardClockSwitchController
constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT)
connect(
@@ -182,7 +176,6 @@ constructor(
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
listOf(smartspaceView, dateWeatherView).forEach {
it?.let {
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 729759a9ad00..5d463f72d8b2 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.keyguard.MigrateClocksToBlueprint
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.shade.ShadeDisplayAware
@@ -50,16 +49,13 @@ constructor(
sharedNotificationContainerBinder,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!MigrateClocksToBlueprint.isEnabled) {
- return
- }
constraintSet.apply {
connect(
R.id.nssl_placeholder,
TOP,
PARENT_ID,
TOP,
- context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
)
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/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 1c897237fe89..fb311a533aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -24,7 +24,6 @@ import com.android.app.animation.Interpolators
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -194,12 +193,7 @@ constructor(
(!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE
val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
- val translationY =
- if (MigrateClocksToBlueprint.isEnabled) {
- max(params.topInset - params.minViewY, burnInY)
- } else {
- max(params.topInset, params.minViewY + burnInY) - params.minViewY
- }
+ val translationY = max(params.topInset - params.minViewY, burnInY)
BurnInModel(
translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
translationY = translationY,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt
new file mode 100644
index 000000000000..890628c31c55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+class KeyguardQuickAffordanceHapticViewModel
+@AssistedInject
+constructor(
+ @Assisted quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel>,
+ private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+) {
+
+ private val activatedHistory = MutableStateFlow(ActivatedHistory(false))
+
+ private val launchingHapticState: Flow<HapticState> =
+ combine(
+ quickAffordanceViewModel.map { it.configKey },
+ quickAffordanceInteractor.launchingFromTriggeredResult,
+ ) { key, launchingResult ->
+ val validKey = key != null && key == launchingResult?.configKey
+ if (validKey && launchingResult?.launched == true) {
+ HapticState.LAUNCH
+ } else {
+ HapticState.NO_HAPTICS
+ }
+ }
+ .distinctUntilChanged()
+
+ private val toggleHapticState: Flow<HapticState> =
+ activatedHistory
+ .map { history ->
+ when {
+ history.previousValue == false && history.currentValue -> HapticState.TOGGLE_ON
+ history.previousValue == true && !history.currentValue -> HapticState.TOGGLE_OFF
+ else -> HapticState.NO_HAPTICS
+ }
+ }
+ .distinctUntilChanged()
+
+ val quickAffordanceHapticState =
+ merge(launchingHapticState, toggleHapticState).distinctUntilChanged()
+
+ fun resetLaunchingFromTriggeredResult() =
+ quickAffordanceInteractor.setLaunchingFromTriggeredResult(null)
+
+ fun updateActivatedHistory(isActivated: Boolean) {
+ activatedHistory.value =
+ ActivatedHistory(
+ currentValue = isActivated,
+ previousValue = activatedHistory.value.currentValue,
+ )
+ }
+
+ enum class HapticState {
+ TOGGLE_ON,
+ TOGGLE_OFF,
+ LAUNCH,
+ NO_HAPTICS,
+ }
+
+ private data class ActivatedHistory(
+ val currentValue: Boolean,
+ val previousValue: Boolean? = null,
+ )
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel>
+ ): KeyguardQuickAffordanceHapticViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 9066d466ceca..eaba5d5a149c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -29,7 +29,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor
import com.android.systemui.keyguard.shared.model.Edge
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -130,7 +129,6 @@ constructor(
PrimaryBouncerToLockscreenTransitionViewModel,
private val screenOffAnimationController: ScreenOffAnimationController,
private val aodBurnInViewModel: AodBurnInViewModel,
- private val aodAlphaViewModel: AodAlphaViewModel,
private val shadeInteractor: ShadeInteractor,
) {
val burnInLayerVisibility: Flow<Int> =
@@ -284,15 +282,6 @@ constructor(
.distinctUntilChanged()
}
- /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
- @Deprecated("only used for legacy status view")
- fun lockscreenStateAlpha(viewState: ViewStateAccessor): Flow<Float> {
- return aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState)
- }
-
- /** For elements that appear and move during the animation -> AOD */
- val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha
-
val translationY: Flow<Float> = aodBurnInViewModel.movement.map { it.translationY.toFloat() }
val translationX: Flow<StateToValue> =
diff --git a/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt
new file mode 100644
index 000000000000..dd2525f5ca45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lottie/LottieTaskExt.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.lottie
+
+import com.airbnb.lottie.LottieComposition
+import com.airbnb.lottie.LottieListener
+import com.airbnb.lottie.LottieTask
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Suspends until [LottieTask] is finished with a result or a failure.
+ *
+ * @return result of the [LottieTask] when it's successful
+ */
+suspend fun LottieTask<LottieComposition>.await() =
+ suspendCancellableCoroutine<LottieComposition> { continuation ->
+ val resultListener =
+ LottieListener<LottieComposition> { result ->
+ with(continuation) { if (!isCancelled && !isCompleted) resume(result) }
+ }
+ val failureListener =
+ LottieListener<Throwable> { throwable ->
+ with(continuation) {
+ if (!isCancelled && !isCompleted) resumeWithException(throwable)
+ }
+ }
+ addListener(resultListener)
+ addFailureListener(failureListener)
+ continuation.invokeOnCancellation {
+ removeListener(resultListener)
+ removeFailureListener(failureListener)
+ }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index c32bd403d2e8..b4dabbe036e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -34,13 +34,14 @@ import android.view.ViewGroup
import android.view.ViewGroupOverlay
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.keyguard.KeyguardViewController
import com.android.systemui.Flags.mediaControlsLockscreenShadeBugFix
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -68,7 +69,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
private val TAG: String = MediaHierarchyManager::class.java.simpleName
@@ -115,7 +115,7 @@ constructor(
wakefulnessLifecycle: WakefulnessLifecycle,
shadeInteractor: ShadeInteractor,
private val secureSettings: SecureSettings,
- @Main private val handler: Handler,
+ @Background private val handler: Handler,
@Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
@@ -631,7 +631,7 @@ constructor(
}
}
}
- secureSettings.registerContentObserverForUserSync(
+ secureSettings.registerContentObserverForUserAsync(
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
settingsObserver,
UserHandle.USER_ALL,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 574ccee28faa..ab998d10287f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -366,7 +366,6 @@ public abstract class MediaOutputBaseAdapter extends
/ (double) seekBar.getMax());
mVolumeValueText.setText(mContext.getResources().getString(
R.string.media_output_dialog_volume_percentage, percentage));
- mVolumeValueText.setVisibility(View.VISIBLE);
if (mStartFromMute) {
updateUnmutedVolumeIcon(device);
mStartFromMute = false;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 311cbfb7e632..b2696aeaabfc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -132,7 +132,7 @@ constructor(
val isDefaultNotesAppSet =
noteTaskInfoResolver.resolveInfo(
QUICK_AFFORDANCE,
- user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+ user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE),
) != null
return when {
isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
@@ -158,7 +158,7 @@ constructor(
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
- return OnTriggeredResult.Handled
+ return OnTriggeredResult.Handled(true)
}
}
@@ -194,7 +194,7 @@ private fun RoleManager.createNotesRoleFlow(
fun isDefaultNotesAppSetForUser() =
noteTaskInfoResolver.resolveInfo(
QUICK_AFFORDANCE,
- user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
+ user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE),
) != null
trySendBlocking(isDefaultNotesAppSetForUser())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 91a3120ec770..1e608af14568 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -67,6 +67,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.DeviceConfigProxy
@@ -141,7 +142,6 @@ interface FgsManagerController {
class FgsManagerControllerImpl
@Inject
constructor(
- @ShadeDisplayAware private val context: Context,
@ShadeDisplayAware private val resources: Resources,
@Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
@@ -155,6 +155,7 @@ constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager,
private val systemUIDialogFactory: SystemUIDialog.Factory,
+ private val shadeDialogContextRepository: ShadeDialogContextInteractor,
) : Dumpable, FgsManagerController {
companion object {
@@ -388,7 +389,7 @@ constructor(
override fun showDialog(expandable: Expandable?) {
synchronized(lock) {
if (dialog == null) {
- val dialog = systemUIDialogFactory.create(context)
+ val dialog = systemUIDialogFactory.create(shadeDialogContextRepository.context)
dialog.setTitle(R.string.fgs_manager_dialog_title)
dialog.setMessage(R.string.fgs_manager_dialog_message)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
index ef7e7eb59898..84b995e1cd28 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileIcon.kt
@@ -22,16 +22,21 @@ import com.android.systemui.qs.tileimpl.QSTileImpl
/**
* Creates a [QSTile.Icon] from an [Icon].
- * * [Icon.Loaded] -> [QSTileImpl.DrawableIcon]
+ * * [Icon.Loaded] with null [res] -> [QSTileImpl.DrawableIcon]
+ * * [Icon.Loaded] & with non null [res] -> [QSTileImpl.DrawableIconWithRes]
* * [Icon.Resource] -> [QSTileImpl.ResourceIcon]
*/
fun Icon.asQSTileIcon(): QSTile.Icon {
return when (this) {
is Icon.Loaded -> {
- QSTileImpl.DrawableIcon(this.drawable)
+ if (res == null) {
+ QSTileImpl.DrawableIcon(drawable)
+ } else {
+ QSTileImpl.DrawableIconWithRes(drawable, res)
+ }
}
is Icon.Resource -> {
- QSTileImpl.ResourceIcon.get(this.res)
+ QSTileImpl.ResourceIcon.get(res)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
index 790793eab258..3049a40f18c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -17,16 +17,16 @@
package com.android.systemui.qs.composefragment.ui
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.graphics.layer.CompositingStrategy
-import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.graphics.graphicsLayer
/**
* Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
@@ -34,16 +34,16 @@ import androidx.compose.ui.graphics.layer.drawLayer
* from the QS container.
*/
fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
- return this.drawWithCache {
+ return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+ .drawWithContent {
+ drawContent()
val params = clipParams()
val left = -params.leftInset.toFloat()
val right = size.width + params.rightInset.toFloat()
val top = params.top.toFloat()
val bottom = params.bottom.toFloat()
- val graphicsLayer = obtainGraphicsLayer()
- graphicsLayer.compositingStrategy = CompositingStrategy.Offscreen
- graphicsLayer.record {
- drawContent()
+ val clipSize = Size(right - left, bottom - top)
+ if (!clipSize.isEmpty()) {
clipRect {
drawRoundRect(
color = Color.Black,
@@ -54,9 +54,6 @@ fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams
)
}
}
- onDrawWithContent {
- drawLayer(graphicsLayer)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 873059ee08db..e7fa27159e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -675,17 +675,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
}
private void add() {
- if (addFromPosition(getLayoutPosition())) {
- itemView.announceForAccessibility(
- itemView.getContext().getText(R.string.accessibility_qs_edit_tile_added));
- }
+ addFromPosition(getLayoutPosition());
}
private void remove() {
- if (removeFromPosition(getLayoutPosition())) {
- itemView.announceForAccessibility(
- itemView.getContext().getText(R.string.accessibility_qs_edit_tile_removed));
- }
+ removeFromPosition(getLayoutPosition());
}
boolean isCurrentTile() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
index 85db95203b45..f3c06a481fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
@@ -54,7 +54,7 @@ fun EditModeButton(
) {
Icon(
imageVector = Icons.Default.Edit,
- contentDescription = stringResource(id = R.string.qs_edit),
+ contentDescription = stringResource(id = R.string.accessibility_quick_settings_edit),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 42a0cb1004f4..b7ff63cdc1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -41,6 +41,7 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -56,6 +57,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
private final DataSaverController mDataSaverController;
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final SystemUIDialog.Factory mSystemUIDialogFactory;
+ private final ShadeDialogContextInteractor mShadeDialogContextInteractor;
@Inject
public DataSaverTile(
@@ -70,13 +72,15 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
QSLogger qsLogger,
DataSaverController dataSaverController,
DialogTransitionAnimator dialogTransitionAnimator,
- SystemUIDialog.Factory systemUIDialogFactory
+ SystemUIDialog.Factory systemUIDialogFactory,
+ ShadeDialogContextInteractor shadeDialogContextInteractor
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDataSaverController = dataSaverController;
mDialogTransitionAnimator = dialogTransitionAnimator;
mSystemUIDialogFactory = systemUIDialogFactory;
+ mShadeDialogContextInteractor = shadeDialogContextInteractor;
mDataSaverController.observe(getLifecycle(), this);
}
@@ -102,7 +106,8 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
// Show a dialog to confirm first. Dialogs shown by the DialogTransitionAnimator must be
// created and shown on the main thread, so we post it to the UI handler.
mUiHandler.post(() -> {
- SystemUIDialog dialog = mSystemUIDialogFactory.create(mContext);
+ SystemUIDialog dialog = mSystemUIDialogFactory.create(
+ mShadeDialogContextInteractor.getContext());
dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
dialog.setMessage(com.android.internal.R.string.data_saver_description);
dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
index 989fc0fd6f44..5ba1527dbf69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotesTile.kt
@@ -22,6 +22,7 @@ import android.os.Looper
import android.service.quicksettings.Tile
import com.android.internal.logging.MetricsLogger
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -92,7 +93,8 @@ constructor(
state?.apply {
this.state = tileState.activationState.legacyState
- icon = maybeLoadResourceIcon(tileState.iconRes ?: R.drawable.ic_qs_notes)
+ icon =
+ maybeLoadResourceIcon((tileState.icon as Icon.Loaded).res ?: R.drawable.ic_qs_notes)
label = tileState.label
contentDescription = tileState.contentDescription
expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index 19b45d50c594..7516ca030d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -193,7 +193,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern
if (mJob == null) {
mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(),
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> {
- mInternetDialogController.startActivity(intent, view);
+ mInternetDialogController.startActivityForDialog(intent);
return null;
}, () -> {
wifiConnect(wifiEntry, view);
@@ -211,7 +211,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern
true /* connectForCaller */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- mContext.startActivity(intent);
+ mInternetDialogController.startActivityForDialog(intent);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index dbe1ae90b3f6..7036ef914a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -781,6 +781,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller);
}
+ void startActivityForDialog(Intent intent) {
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ }
+
void launchNetworkSetting(View view) {
startActivity(getSettingsIntent(), view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 70c2a2a0d55a..5e9deec58c58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -72,6 +72,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeDisplayAware;
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wifitrackerlib.WifiEntry;
@@ -104,9 +105,9 @@ public class InternetDialogDelegate implements
private final Handler mHandler;
private final Executor mBackgroundExecutor;
private final DialogTransitionAnimator mDialogTransitionAnimator;
- private final Context mContext;
private final boolean mAboveStatusBar;
private final SystemUIDialog.Factory mSystemUIDialogFactory;
+ private final ShadeDialogContextInteractor mShadeDialogContextInteractor;
@VisibleForTesting
protected InternetAdapter mAdapter;
@@ -204,10 +205,11 @@ public class InternetDialogDelegate implements
@Main Handler handler,
@Background Executor executor,
KeyguardStateController keyguardStateController,
- SystemUIDialog.Factory systemUIDialogFactory) {
- mContext = context;
+ SystemUIDialog.Factory systemUIDialogFactory,
+ ShadeDialogContextInteractor shadeDialogContextInteractor) {
mAboveStatusBar = aboveStatusBar;
mSystemUIDialogFactory = systemUIDialogFactory;
+ mShadeDialogContextInteractor = shadeDialogContextInteractor;
if (DEBUG) {
Log.d(TAG, "Init InternetDialog");
}
@@ -230,7 +232,8 @@ public class InternetDialogDelegate implements
@Override
public SystemUIDialog createDialog() {
- SystemUIDialog dialog = mSystemUIDialogFactory.create(this, mContext);
+ SystemUIDialog dialog = mSystemUIDialogFactory.create(this,
+ mShadeDialogContextInteractor.getContext());
if (!mAboveStatusBar) {
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 34c2ec90f1e8..80d429ce2716 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -35,13 +35,13 @@ constructor(@ShadeDisplayAware private val resources: Resources, val theme: Them
override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes =
+ val iconRes =
if (data.isEnabled) {
R.drawable.qs_airplane_icon_on
} else {
R.drawable.qs_airplane_icon_off
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index a72992db4496..d56d9944dbb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -84,8 +84,8 @@ constructor(
secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm)
}
}
- iconRes = R.drawable.ic_alarm
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = R.drawable.ic_alarm
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index e116d8cef2ee..72759c5bb066 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -38,10 +38,10 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.battery_detail_switch_title)
contentDescription = label
- iconRes =
+ val iconRes =
if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
else R.drawable.qs_battery_saver_icon_off
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index 21b9f659dde4..e5a0fe8ed048 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -37,8 +37,8 @@ constructor(
override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
- iconRes = R.drawable.ic_qs_color_correction
- icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
+ val iconRes = R.drawable.ic_qs_color_correction
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 2dfb1fc4fe98..32ccba6f1fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -35,14 +35,14 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes =
+ val iconRes =
if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
R.drawable.qs_flashlight_icon_on
} else {
R.drawable.qs_flashlight_icon_off
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 7f41cbd322dd..c571b136e18b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -36,8 +36,8 @@ constructor(
override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes = R.drawable.ic_qs_font_scaling
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = R.drawable.ic_qs_font_scaling
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription = label
activationState = QSTileState.ActivationState.ACTIVE
sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 4c302b363c3b..12f71491c7b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -37,8 +37,8 @@ constructor(
override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_hearing_devices_label)
- iconRes = R.drawable.qs_hearing_devices_icon
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = R.drawable.qs_hearing_devices_icon
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index 1a6876d0b765..7ad01e463399 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,11 +61,11 @@ constructor(
when (val dataIcon = data.icon) {
is InternetTileIconModel.ResourceId -> {
- iconRes = dataIcon.resId
icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resId, theme),
contentDescription = null,
+ dataIcon.resId,
)
}
@@ -76,11 +76,11 @@ constructor(
}
is InternetTileIconModel.Satellite -> {
- iconRes = dataIcon.resourceIcon.res // level is inferred from res
icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resourceIcon.res, theme),
contentDescription = null,
+ dataIcon.resourceIcon.res,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 8d35b2413bad..05590e803ffa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -35,7 +35,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
-
+ val iconRes: Int
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
@@ -45,7 +45,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
secondaryLabel = subtitleArray[1]
iconRes = R.drawable.qs_invert_colors_icon_off
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index 3557c1a4ac9d..afb137e1e92f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -39,6 +39,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
Icon.Loaded(
resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
null,
+ R.drawable.qs_record_issue_icon_on,
)
} else {
activationState = QSTileState.ActivationState.INACTIVE
@@ -46,6 +47,7 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
Icon.Loaded(
resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
null,
+ R.drawable.qs_record_issue_icon_off,
)
}
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index dfc24a10c491..ced5a4f099a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -35,13 +35,13 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes =
+ val iconRes =
if (data.isEnabled) {
R.drawable.qs_location_icon_on
} else {
R.drawable.qs_location_icon_off
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
label = resources.getString(R.string.quick_settings_location_label)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 9b2880b6d47f..479f61823912 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -17,10 +17,10 @@
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import android.content.Context
+import android.graphics.drawable.Drawable
import android.os.UserHandle
import com.android.app.tracing.coroutines.flow.flowName
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.modes.shared.ModesUiIcons
@@ -31,7 +31,6 @@ import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
-import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -68,37 +67,29 @@ constructor(
suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
- if (ModesUiIcons.isEnabled) {
- val tileIcon = getTileIcon(activeModes.mainMode)
- return ModesTileModel(
- isActivated = activeModes.isAnyActive(),
- icon = tileIcon.icon,
- iconResId = tileIcon.resId,
- activeModes = activeModes.modeNames,
- )
- } else {
- return ModesTileModel(
- isActivated = activeModes.isAnyActive(),
- icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(),
- iconResId = ModesTile.ICON_RES_ID,
- activeModes = activeModes.modeNames,
- )
- }
- }
+ val drawable: Drawable
+ val iconRes: Int?
+ val activeMode = activeModes.mainMode
- private data class TileIcon(val icon: Icon.Loaded, val resId: Int?)
-
- private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon {
- return if (activeMode != null) {
+ if (ModesUiIcons.isEnabled && activeMode != null) {
// ZenIconKey.resPackage is null if its resId is a system icon.
- if (activeMode.icon.key.resPackage == null) {
- TileIcon(activeMode.icon.drawable.asIcon(), activeMode.icon.key.resId)
- } else {
- TileIcon(activeMode.icon.drawable.asIcon(), null)
- }
+ iconRes =
+ if (activeMode.icon.key.resPackage == null) {
+ activeMode.icon.key.resId
+ } else {
+ null
+ }
+ drawable = activeMode.icon.drawable
} else {
- TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID)
+ iconRes = ModesTile.ICON_RES_ID
+ drawable = context.getDrawable(iconRes)!!
}
+
+ return ModesTileModel(
+ isActivated = activeModes.isAnyActive(),
+ icon = Icon.Loaded(drawable, null, iconRes),
+ activeModes = activeModes.modeNames,
+ )
}
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(ModesUi.isEnabled)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index db4812342050..d0eacbc9a957 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -21,12 +21,10 @@ import com.android.systemui.common.shared.model.Icon
data class ModesTileModel(
val isActivated: Boolean,
val activeModes: List<String>,
- val icon: Icon.Loaded,
-
/**
- * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a
- * resource with a known id in SystemUI (such as resources from `android.R`,
- * `com.android.internal.R`, or `com.android.systemui.res` itself).
+ * icon.res will only be present if it is known to correspond to a resource with a known id in
+ * SystemUI (such as resources from `android.R`, `com.android.internal.R`, or
+ * `com.android.systemui.res` itself).
*/
- val iconResId: Int? = null
+ val icon: Icon.Loaded,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 1507ef4b3b58..99ae3b8db709 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -34,7 +34,6 @@ constructor(@ShadeDisplayAware private val resources: Resources, val theme: Reso
QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes = data.iconResId
icon = data.icon
activationState =
if (data.isActivated) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index 3569e4d0b42c..16b36289ad95 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -49,7 +49,7 @@ constructor(
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
sideViewIcon = QSTileState.SideViewIcon.None
-
+ val iconRes: Int
if (data.isActivated) {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_nightlight_icon_on
@@ -58,7 +58,7 @@ constructor(
iconRes = R.drawable.qs_nightlight_icon_off
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
secondaryLabel = getSecondaryLabel(data, resources)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
index a5436192af39..ecdd71170cda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/notes/domain/NotesTileMapper.kt
@@ -35,8 +35,8 @@ constructor(
) : QSTileDataToStateMapper<NotesTileModel> {
override fun map(config: QSTileConfig, data: NotesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- iconRes = R.drawable.ic_qs_notes
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ val iconRes = R.drawable.ic_qs_notes
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription = label
activationState = QSTileState.ActivationState.INACTIVE
sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 76f1e8b8760c..5b3ea93ab1ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -38,8 +38,8 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
label = resources.getString(R.string.quick_settings_onehanded_label)
- iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index c546250e73d2..21e92d3a1972 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -38,8 +38,8 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.qr_code_scanner_title)
contentDescription = label
- iconRes = R.drawable.ic_qr_code_scanner
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = R.drawable.ic_qr_code_scanner
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
sideViewIcon = QSTileState.SideViewIcon.Chevron
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 66d0f96fdcde..66759cdfd1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -37,6 +37,7 @@ constructor(
override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
+ val iconRes: Int
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_extra_dim_icon_on
@@ -50,7 +51,7 @@ constructor(
resources
.getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
label =
resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index a0144221577d..000c7025e32b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -42,7 +42,7 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
-
+ val iconRes: Int
if (data.isRotationLocked) {
activationState = QSTileState.ActivationState.INACTIVE
secondaryLabel = EMPTY_SECONDARY_STRING
@@ -57,7 +57,7 @@ constructor(
}
iconRes = R.drawable.qs_auto_rotate_icon_on
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
if (isDeviceFoldable(resources, deviceStateManager)) {
secondaryLabel = getSecondaryLabelWithPosture(activationState)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index aea4967c546c..1d5cf29f2462 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -36,6 +36,7 @@ constructor(
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
with(data) {
+ val iconRes: Int
if (isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_data_saver_icon_on
@@ -45,7 +46,7 @@ constructor(
iconRes = R.drawable.qs_data_saver_icon_off
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index f3136e015acf..0a61e3cbe616 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -38,7 +38,7 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_screen_record_label)
supportedActions = setOf(QSTileState.UserAction.CLICK)
-
+ val iconRes: Int
when (data) {
is ScreenRecordModel.Recording -> {
activationState = QSTileState.ActivationState.ACTIVE
@@ -61,7 +61,7 @@ constructor(
resources.getString(R.string.quick_settings_screen_record_start)
}
}
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
contentDescription =
if (TextUtils.isEmpty(secondaryLabel)) label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 73e61b7d178e..f54f46c01dee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -50,8 +50,8 @@ constructor(
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
- iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ val iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index e9aa46c5f253..5933d65bc61f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -116,11 +116,11 @@ constructor(@ShadeDisplayAware private val resources: Resources, private val the
}
}
- iconRes =
+ val iconRes =
if (activationState == QSTileState.ActivationState.ACTIVE)
R.drawable.qs_light_dark_theme_icon_on
else R.drawable.qs_light_dark_theme_icon_off
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
supportedActions =
if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index 6a3195a493c8..5b462ba074ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -41,8 +41,8 @@ constructor(
QSTileState.build(resources, theme, config.uiConfig) {
label = getTileLabel()!!
contentDescription = label
- iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
- icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
+ icon = Icon.Loaded(resources.getDrawable(iconRes, theme), null, iconRes)
when (data) {
is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 8394be5e0a38..c6af729cd4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -36,7 +36,6 @@ import kotlin.reflect.KClass
*/
data class QSTileState(
val icon: Icon?,
- val iconRes: Int?,
val label: CharSequence,
val activationState: ActivationState,
val secondaryLabel: CharSequence?,
@@ -58,7 +57,7 @@ data class QSTileState(
): QSTileState {
val iconDrawable = resources.getDrawable(config.iconRes, theme)
return build(
- Icon.Loaded(iconDrawable, null),
+ Icon.Loaded(iconDrawable, null, config.iconRes),
resources.getString(config.labelRes),
builder,
)
@@ -115,7 +114,6 @@ data class QSTileState(
}
class Builder(var icon: Icon?, var label: CharSequence) {
- var iconRes: Int? = null
var activationState: ActivationState = ActivationState.INACTIVE
var secondaryLabel: CharSequence? = null
var supportedActions: Set<UserAction> = setOf(UserAction.CLICK)
@@ -128,7 +126,6 @@ data class QSTileState(
fun build(): QSTileState =
QSTileState(
icon,
- iconRes,
label,
activationState,
secondaryLabel,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 632eeefcb462..c34edc81bfe7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -260,8 +260,8 @@ constructor(
icon =
when (val stateIcon = viewModelState.icon) {
is Icon.Loaded ->
- if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
- else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
+ if (stateIcon.res == null) DrawableIcon(stateIcon.drawable)
+ else DrawableIconWithRes(stateIcon.drawable, stateIcon.res)
is Icon.Resource -> ResourceIcon.get(stateIcon.res)
null -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 8c54ab40c680..862dba1e7294 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.user
import android.app.Dialog
-import android.content.Context
import android.content.DialogInterface
import android.content.DialogInterface.BUTTON_NEUTRAL
import android.content.Intent
@@ -34,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.user.ui.dialog.DialogShowerImpl
import javax.inject.Inject
@@ -50,6 +50,7 @@ constructor(
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val uiEventLogger: UiEventLogger,
private val dialogFactory: SystemUIDialog.Factory,
+ private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
) {
companion object {
@@ -63,7 +64,8 @@ constructor(
* Populate the dialog with information from and adapter obtained from
* [userDetailViewAdapterProvider] and show it as launched from [expandable].
*/
- fun showDialog(context: Context, expandable: Expandable) {
+ fun showDialog(expandable: Expandable) {
+ val context = shadeDialogContextInteractor.context
with(dialogFactory.create(context)) {
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index a7b51faaed57..10ac2cf76763 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -16,6 +16,8 @@
package com.android.systemui.scrim;
+import static com.android.systemui.Flags.notificationShadeBlur;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -214,8 +216,7 @@ public class ScrimDrawable extends Drawable {
public void draw(@NonNull Canvas canvas) {
mPaint.setColor(mMainColor);
mPaint.setAlpha(mAlpha);
- if (WindowBlurFlag.isEnabled()) {
- // TODO(b/370555223): Match the alpha to the visual spec when it is finalized.
+ if (notificationShadeBlur() || WindowBlurFlag.isEnabled()) {
// TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition
mPaint.setAlpha((int) (0.5f * mAlpha));
}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index 4bfa61e9dcd4..0f80e7432a54 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -16,6 +16,8 @@
package com.android.systemui.scrim;
+import static com.android.systemui.Flags.notificationShadeBlur;
+
import static java.lang.Float.isNaN;
import android.annotation.NonNull;
@@ -39,13 +41,12 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.res.R;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
import java.util.concurrent.Executor;
-import static com.android.systemui.Flags.notificationShadeBlur;
-
/**
* A view which can draw a scrim. This view maybe be used in multiple windows running on different
* threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we
@@ -253,8 +254,11 @@ public class ScrimView extends View {
mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, tintAmount);
}
if (notificationShadeBlur()) {
- // TODO(b/370555223): Fix color and transparency to match visual spec exactly
- mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), Color.GRAY, 0.5f);
+ int layerAbove = ColorUtils.setAlphaComponent(
+ getResources().getColor(R.color.shade_panel, null),
+ (int) (0.4f * 255));
+ int layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (int) (0.1f * 255));
+ mainTinted = ColorUtils.compositeColors(layerAbove, layerBelow);
}
drawable.setColor(mainTinted, animated);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index e1631ccdcb06..bbb13d5c1dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -61,9 +61,18 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
/** Callback for notifying of changes. */
@WeaklyReferencedCallback
interface Callback {
- /** Notifies that the current user will be changed. */
+ /**
+ * Same as {@link onBeforeUserSwitching(Int, Runnable)} but the callback will be called
+ * automatically after the completion of this method.
+ */
fun onBeforeUserSwitching(newUser: Int) {}
+ /** Notifies that the current user will be changed. */
+ fun onBeforeUserSwitching(newUser: Int, resultCallback: Runnable) {
+ onBeforeUserSwitching(newUser)
+ resultCallback.run()
+ }
+
/**
* Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be called
* automatically after the completion of this method.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index b7a3aedc565e..42d83637ec1a 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -196,8 +196,9 @@ internal constructor(
private fun registerUserSwitchObserver() {
iActivityManager.registerUserSwitchObserver(
object : UserSwitchObserver() {
- override fun onBeforeUserSwitching(newUserId: Int) {
+ override fun onBeforeUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
handleBeforeUserSwitching(newUserId)
+ reply?.sendResult(null)
}
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -236,8 +237,7 @@ internal constructor(
setUserIdInternal(newUserId)
notifySubscribers { callback, resultCallback ->
- callback.onBeforeUserSwitching(newUserId)
- resultCallback.run()
+ callback.onBeforeUserSwitching(newUserId, resultCallback)
}
.await()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index f2c39063c867..839d4596bb7c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -48,7 +48,6 @@ import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -367,9 +366,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
mTouchActive = true;
mTouchCancelled = false;
mDownEvent = ev;
- if (MigrateClocksToBlueprint.isEnabled()) {
- mService.userActivity();
- }
+ mService.userActivity();
} else if (ev.getActionMasked() == MotionEvent.ACTION_UP
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
mTouchActive = false;
@@ -443,8 +440,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
float x = ev.getRawX();
float y = ev.getRawY();
if (mStatusBarViewController.touchIsWithinView(x, y)) {
- if (!(MigrateClocksToBlueprint.isEnabled()
- && mPrimaryBouncerInteractor.isBouncerShowing())) {
+ if (!mPrimaryBouncerInteractor.isBouncerShowing()) {
if (mStatusBarWindowStateController.windowIsShowing()) {
mIsTrackingBarGesture = true;
return logDownDispatch(ev, "sending touch to status bar",
@@ -453,7 +449,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
return logDownDispatch(ev, "hidden or hiding", true);
}
} else {
- mShadeLogger.d("NSWVC: bouncer not showing");
+ mShadeLogger.d("NSWVC: bouncer showing");
}
} else {
mShadeLogger.d("NSWVC: touch not within view");
@@ -511,34 +507,24 @@ public class NotificationShadeWindowViewController implements Dumpable {
&& !bouncerShowing
&& !mStatusBarStateController.isDozing()) {
if (mDragDownHelper.isDragDownEnabled()) {
- if (MigrateClocksToBlueprint.isEnabled()) {
- // When on lockscreen, if the touch originates at the top of the screen
- // go directly to QS and not the shade
- if (mStatusBarStateController.getState() == KEYGUARD
- && mQuickSettingsController.shouldQuickSettingsIntercept(
- ev.getX(), ev.getY(), 0)) {
- mShadeLogger.d("NSWVC: QS intercepted");
- return true;
- }
+ // When on lockscreen, if the touch originates at the top of the screen go
+ // directly to QS and not the shade
+ if (mStatusBarStateController.getState() == KEYGUARD
+ && mQuickSettingsController.shouldQuickSettingsIntercept(
+ ev.getX(), ev.getY(), 0)) {
+ mShadeLogger.d("NSWVC: QS intercepted");
+ return true;
}
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (MigrateClocksToBlueprint.isEnabled()) {
- if (result) {
- mLastInterceptWasDragDownHelper = true;
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mShadeLogger.d("NSWVC: drag down helper intercepted");
- }
- } else if (didNotificationPanelInterceptEvent(ev)) {
- return true;
- }
- } else {
- if (result) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mShadeLogger.d("NSWVC: drag down helper intercepted");
- }
+ if (result) {
+ mLastInterceptWasDragDownHelper = true;
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mShadeLogger.d("NSWVC: drag down helper intercepted");
}
+ } else if (didNotificationPanelInterceptEvent(ev)) {
+ return true;
}
return result;
} else {
@@ -547,12 +533,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
return true;
}
}
- } else if (MigrateClocksToBlueprint.isEnabled()) {
+ } else if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
// This final check handles swipes on HUNs and when Pulsing
- if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
- mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
- return true;
- }
+ mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
+ return true;
}
return false;
}
@@ -562,9 +546,6 @@ public class NotificationShadeWindowViewController implements Dumpable {
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mShadeViewController.handleExternalInterceptTouch(cancellation);
- }
cancellation.recycle();
}
@@ -574,22 +555,12 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (mStatusBarStateController.isDozing()) {
handled = !mDozeServiceHost.isPulsing();
}
- if (MigrateClocksToBlueprint.isEnabled()) {
- if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
- // we still want to finish our drag down gesture when locking the screen
- handled |= mDragDownHelper.onTouchEvent(ev) || handled;
- }
- if (!handled && mShadeViewController.handleExternalTouch(ev)) {
- return true;
- }
- } else {
- if (mDragDownHelper.isDragDownEnabled()
- || mDragDownHelper.isDraggingDown()) {
- // we still want to finish our drag down gesture when locking the screen
- return mDragDownHelper.onTouchEvent(ev) || handled;
- } else {
- return handled;
- }
+ if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
+ // we still want to finish our drag down gesture when locking the screen
+ handled |= mDragDownHelper.onTouchEvent(ev) || handled;
+ }
+ if (!handled && mShadeViewController.handleExternalTouch(ev)) {
+ return true;
}
return handled;
}
@@ -673,14 +644,12 @@ public class NotificationShadeWindowViewController implements Dumpable {
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (MigrateClocksToBlueprint.isEnabled()) {
- // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
- // to also ask NotificationPanelViewController directly, in order to process swipe up
- // events originating from notifications
- if (mShadeViewController.handleExternalInterceptTouch(ev)) {
- mShadeLogger.d("NSWVC: NPVC intercepted");
- return true;
- }
+ // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need to
+ // also ask NotificationPanelViewController directly, in order to process swipe up events
+ // originating from notifications
+ if (mShadeViewController.handleExternalInterceptTouch(ev)) {
+ mShadeLogger.d("NSWVC: NPVC intercepted");
+ return true;
}
return false;
@@ -707,9 +676,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (!SceneContainerFlag.isEnabled()) {
mAmbientState.setSwipingUp(false);
}
- if (MigrateClocksToBlueprint.isEnabled()) {
- mDragDownHelper.stopDragging();
- }
+ mDragDownHelper.stopDragging();
}
private void setBrightnessMirrorShowingForDepth(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 207439e1f374..58111576574e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -21,7 +21,6 @@ import android.view.ViewGroup
import android.view.WindowInsets
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
@@ -32,7 +31,6 @@ import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -275,7 +273,6 @@ constructor(
constraintSet.clone(mView)
setKeyguardStatusViewConstraints(constraintSet)
setQsConstraints(constraintSet)
- setNotificationsConstraints(constraintSet)
setLargeScreenShadeHeaderConstraints(constraintSet)
mView.applyConstraints(constraintSet)
}
@@ -288,21 +285,6 @@ constructor(
}
}
- private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
- if (MigrateClocksToBlueprint.isEnabled) {
- return
- }
- val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
- val nsslId = R.id.notification_stack_scroller
- constraintSet.apply {
- connect(nsslId, START, startConstraintId, START)
- setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
- setMargin(nsslId, END, panelMarginHorizontal)
- setMargin(nsslId, TOP, topMargin)
- setMargin(nsslId, BOTTOM, notificationsBottomMargin)
- }
- }
-
private fun setQsConstraints(constraintSet: ConstraintSet) {
val endConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 13330553b2de..000a666bac0d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -21,7 +21,6 @@ import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAP
import android.app.Fragment;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -33,13 +32,10 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.function.Consumer;
/**
@@ -50,11 +46,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
private View mQsFrame;
private View mStackScroller;
- private View mKeyguardStatusBar;
- private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
- private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
- private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
private QS mQs;
@@ -80,7 +72,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
protected void onFinishInflate() {
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
- mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
void setStackScroller(View stackScroller) {
@@ -160,46 +151,11 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- mDrawingOrderedChildren.clear();
- mLayoutDrawingOrder.clear();
- if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) {
- mDrawingOrderedChildren.add(mKeyguardStatusBar);
- mLayoutDrawingOrder.add(mKeyguardStatusBar);
- }
- if (mQsFrame.getVisibility() == View.VISIBLE) {
- mDrawingOrderedChildren.add(mQsFrame);
- mLayoutDrawingOrder.add(mQsFrame);
- }
- if (mStackScroller.getVisibility() == View.VISIBLE) {
- mDrawingOrderedChildren.add(mStackScroller);
- mLayoutDrawingOrder.add(mStackScroller);
- }
-
- // Let's now find the order that the view has when drawing regularly by sorting
- mLayoutDrawingOrder.sort(mIndexComparator);
- super.dispatchDraw(canvas);
- }
-
- @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
super.dispatchTouchEvent(ev));
}
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (MigrateClocksToBlueprint.isEnabled()) {
- return super.drawChild(canvas, child, drawingTime);
- }
- int layoutIndex = mLayoutDrawingOrder.indexOf(child);
- if (layoutIndex >= 0) {
- return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
- } else {
- return super.drawChild(canvas, child, drawingTime);
- }
- }
-
public void applyConstraints(ConstraintSet constraintSet) {
constraintSet.applyTo(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 0df2299eb8dd..4fb43fdcfdae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -68,7 +68,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -1828,16 +1827,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
"onQsIntercept: down action, QS partially expanded/collapsed");
return true;
}
- // TODO (b/265193930): remove dependency on NPVC
- if (mPanelViewControllerLazy.get().isKeyguardShowing()
- && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
- // Dragging down on the lockscreen statusbar should prohibit other interactions
- // immediately, otherwise we'll wait on the touchslop. This is to allow
- // dragging down to expanded quick settings directly on the lockscreen.
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
- }
- }
if (mExpansionAnimator != null) {
mInitialHeightOnTouch = mExpansionHeight;
mShadeLog.logMotionEvent(event,
@@ -1879,9 +1868,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(
mInitialTouchX, mInitialTouchY, h)) {
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
- }
mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
setTracking(true);
traceQsJank(true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index d31868ca0217..61b9f0819f56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -35,6 +35,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractorImpl
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
import com.android.systemui.shade.display.ShadeDisplayPolicyModule
@@ -216,6 +218,25 @@ object ShadeDisplayAwareModule {
}
@Provides
+ @SysUISingleton
+ fun provideShadeDialogContextInteractor(
+ impl: ShadeDialogContextInteractorImpl
+ ): ShadeDialogContextInteractor = impl
+
+ @Provides
+ @IntoMap
+ @ClassKey(ShadeDialogContextInteractor::class)
+ fun provideShadeDialogContextInteractorCoreStartable(
+ impl: Provider<ShadeDialogContextInteractorImpl>
+ ): CoreStartable {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ impl.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
@IntoMap
@ClassKey(ShadePrimaryDisplayCommand::class)
fun provideShadePrimaryDisplayCommand(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt
new file mode 100644
index 000000000000..455370c726a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/FakeShadeDialogContextInteractor.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import android.content.Context
+
+/** Fake context repository that always returns the same context. */
+class FakeShadeDialogContextInteractor(override val context: Context) :
+ ShadeDialogContextInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
new file mode 100644
index 000000000000..201dc0339a0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.shade.domain.interactor
+
+import android.content.Context
+import android.util.Log
+import android.view.Display
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.app.tracing.traceSection
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
+
+/** Provides the correct context to show dialogs on the shade window, whenever it moves. */
+interface ShadeDialogContextInteractor {
+ /** Context usable to create dialogs on the notification shade display. */
+ val context: Context
+}
+
+@SysUISingleton
+class ShadeDialogContextInteractorImpl
+@Inject
+constructor(
+ @Main private val defaultContext: Context,
+ private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>,
+ private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ @Background private val bgScope: CoroutineScope,
+) : CoreStartable, ShadeDialogContextInteractor {
+
+ override fun start() {
+ if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return
+ bgScope.launchTraced(TAG) {
+ shadeDisplaysRepository.displayId
+ // No need for default display pre-warming.
+ .filter { it != Display.DEFAULT_DISPLAY }
+ .collectLatest { displayId ->
+ // Prewarms the context in the background every time the display changes.
+ // In this way, there will be no main thread delays when a dialog is shown.
+ getContextOrDefault(displayId)
+ }
+ }
+ }
+
+ override val context: Context
+ get() {
+ if (!ShadeWindowGoesAround.isEnabled) {
+ return defaultContext
+ }
+ val displayId = shadeDisplaysRepository.displayId.value
+ return getContextOrDefault(displayId)
+ }
+
+ private fun getContextOrDefault(displayId: Int): Context {
+ return try {
+ traceSection({ "Getting dialog context for displayId=$displayId" }) {
+ displayWindowPropertyRepository.get().get(displayId, DIALOG_WINDOW_TYPE).context
+ }
+ } catch (e: Exception) {
+ // This can happen if the display was disconnected in the meantime.
+ Log.e(
+ TAG,
+ "Couldn't get dialog context for displayId=$displayId. Returning default one",
+ e,
+ )
+ defaultContext
+ }
+ }
+
+ private companion object {
+ const val TAG = "ShadeDialogContextRepo"
+ const val DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index b2ca33a4aecf..a7ad46296e08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -44,13 +44,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -136,7 +134,6 @@ public class StatusBarStateControllerImpl implements
private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE];
// These views are used by InteractionJankMonitor to get callback from HWUI.
private View mView;
- private KeyguardClockSwitch mClockSwitchView;
/**
* If any of the system bars is hidden.
@@ -426,7 +423,6 @@ public class StatusBarStateControllerImpl implements
if ((mView == null || !mView.isAttachedToWindow())
&& (view != null && view.isAttachedToWindow())) {
mView = view;
- mClockSwitchView = view.findViewById(R.id.keyguard_clock_container);
}
mDozeAmountTarget = dozeAmount;
if (animated) {
@@ -511,16 +507,7 @@ public class StatusBarStateControllerImpl implements
/** Returns the id of the currently rendering clock */
public String getClockId() {
- if (MigrateClocksToBlueprint.isEnabled()) {
- return mKeyguardClockInteractorLazy.get().getRenderedClockId();
- }
-
- if (mClockSwitchView == null) {
- Log.e(TAG, "Clock container was missing");
- return KeyguardClockSwitch.MISSING_CLOCK_ID;
- }
-
- return mClockSwitchView.getClockId();
+ return mKeyguardClockInteractorLazy.get().getRenderedClockId();
}
private void beginInteractionJankMonitor() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 66af275bc702..a7dbb47bc609 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
import android.view.View
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
@@ -99,6 +100,17 @@ constructor(
)
}
+ if (Flags.promoteNotificationsAutomatically()) {
+ // When we're promoting notifications automatically, the `when` time set on the
+ // notification will likely just be set to the current time, which would cause the chip
+ // to always show "now". We don't want early testers to get that experience since it's
+ // not what will happen at launch, so just don't show any time.
+ // TODO(b/364653005): Only ignore the `when` time if the notification was
+ // *automatically* promoted (as opposed to being legitimately promoted by the
+ // criteria). We'll need to track that status somehow.
+ return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ }
+
if (this.promotedContent.time == null) {
return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
new file mode 100644
index 000000000000..9f523fc845ab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.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 status bar popup chips flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarPopupChips {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_POPUP_CHIPS
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.statusBarPopupChips()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
new file mode 100644
index 000000000000..1663aebd7287
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.shared.model
+
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * Ids used to track different types of popup chips. Will be used to ensure only one chip is
+ * displaying its popup at a time.
+ */
+sealed class PopupChipId(val value: String) {
+ data object MediaControls : PopupChipId("MediaControls")
+}
+
+/** Model for individual status bar popup chips. */
+sealed class PopupChipModel {
+ abstract val logName: String
+ abstract val chipId: PopupChipId
+
+ data class Hidden(override val chipId: PopupChipId, val shouldAnimate: Boolean = true) :
+ PopupChipModel() {
+ override val logName = "Hidden(id=$chipId, anim=$shouldAnimate)"
+ }
+
+ data class Shown(
+ override val chipId: PopupChipId,
+ val icon: Icon,
+ val chipText: String,
+ val isToggled: Boolean = false,
+ val onToggle: () -> Unit,
+ val onIconPressed: () -> Unit,
+ ) : PopupChipModel() {
+ override val logName = "Shown(id=$chipId, toggled=$isToggled)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
new file mode 100644
index 000000000000..5712be30ccd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Interface for a view model that knows the display requirements for a single type of status bar
+ * popup chip.
+ */
+interface StatusBarPopupChipViewModel {
+ /** A flow modeling the popup chip that should be shown (or not shown). */
+ val chip: StateFlow<PopupChipModel>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
new file mode 100644
index 000000000000..b390f29b166c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View model deciding which system process chips to show in the status bar. Emits a list of
+ * PopupChipModels.
+ */
+@SysUISingleton
+class StatusBarPopupChipsViewModel @Inject constructor(@Background scope: CoroutineScope) {
+ private data class PopupChipBundle(
+ val media: PopupChipModel = PopupChipModel.Hidden(chipId = PopupChipId.MediaControls)
+ )
+
+ private val incomingPopupChipBundle: Flow<PopupChipBundle?> =
+ flowOf(null).stateIn(scope, SharingStarted.Lazily, PopupChipBundle())
+
+ val popupChips: Flow<List<PopupChipModel>> =
+ incomingPopupChipBundle
+ .map { _ -> listOf(null).filterIsInstance<PopupChipModel.Shown>() }
+ .stateIn(scope, SharingStarted.Lazily, emptyList())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 80e8f55b897a..d83acf34ca99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.inflation;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -186,6 +187,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
if (AsyncHybridViewInflation.isEnabled()) {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
+ }
}
mRowContentBindStage.requestRebind(entry, null);
}
@@ -256,10 +260,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseMinimized(isMinimized);
- // TODO b/358403414: use the different types of redaction
- boolean needsRedaction = inflaterParams.getRedactionType() != REDACTION_TYPE_NONE;
+ int redactionType = inflaterParams.getRedactionType();
- if (needsRedaction) {
+ params.setRedactionType(redactionType);
+ if (redactionType != REDACTION_TYPE_NONE) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
} else {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
@@ -276,8 +280,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
}
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
-
- if (inflaterParams.isChildInGroup() && needsRedaction) {
+ if (inflaterParams.isChildInGroup()
+ && redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
} else {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 8a1371f1c415..aa010cf63d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.NotificationDataLayerMod
import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -78,8 +79,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationPanelLogg
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
@@ -92,7 +92,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModesCleanupStartable;
import dagger.Binds;
@@ -105,8 +104,6 @@ import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.CoroutineScope;
-import java.util.Optional;
-
import javax.inject.Provider;
/**
@@ -315,21 +312,17 @@ public interface NotificationsModule {
@ClassKey(ZenModesCleanupStartable.class)
CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup);
- /**
- * Provides {@link
- * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if
- * one of the relevant feature flags is enabled.
- */
+ /** Provides the default implementation of {@link PromotedNotificationContentExtractor} if at
+ * least one of the relevant feature flags is enabled, or an implementation that always returns
+ * null if none are enabled. */
@Provides
@SysUISingleton
- static Optional<PromotedNotificationContentExtractor>
- providePromotedNotificationContentExtractor(
- PromotedNotificationsProvider provider, Context context,
- PromotedNotificationLogger logger) {
+ static PromotedNotificationContentExtractor providesPromotedNotificationContentExtractor(
+ Provider<PromotedNotificationContentExtractorImpl> implProvider) {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger));
+ return implProvider.get();
} else {
- return Optional.empty();
+ return (entry, recoveredBuilder) -> null;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 6756077e5444..d02e17cab534 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -1299,7 +1299,6 @@ public class HeadsUpManagerImpl
}
private NotificationEntry requireEntry() {
- /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
return Objects.requireNonNull(mEntry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index 863c665eb4f5..4e9e3336b86f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -34,15 +34,22 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
import javax.inject.Inject
+interface PromotedNotificationContentExtractor {
+ fun extractContent(
+ entry: NotificationEntry,
+ recoveredBuilder: Notification.Builder,
+ ): PromotedNotificationContentModel?
+}
+
@SysUISingleton
-class PromotedNotificationContentExtractor
+class PromotedNotificationContentExtractorImpl
@Inject
constructor(
private val promotedNotificationsProvider: PromotedNotificationsProvider,
@ShadeDisplayAware private val context: Context,
private val logger: PromotedNotificationLogger,
-) {
- fun extractContent(
+) : PromotedNotificationContentExtractor {
+ override fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
): PromotedNotificationContentModel? {
@@ -169,5 +176,5 @@ private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContent
private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
// TODO: Create NotificationProgressModel.toSkeleton, or something similar.
- contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000)
+ contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 13ad1413e89d..a43f8dbc1b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.promoted
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.core.LogLevel.INFO
-import com.android.systemui.log.dagger.NotificationLog
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -27,7 +26,7 @@ import javax.inject.Inject
class PromotedNotificationLogger
@Inject
-constructor(@NotificationLog private val buffer: LogBuffer) {
+constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
fun logExtractionSkipped(entry: NotificationEntry, reason: String) {
buffer.log(
EXTRACTION_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
new file mode 100644
index 000000000000..0f21514fcc94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class AODPromotedNotificationInteractor
+@Inject
+constructor(activeNotificationsInteractor: ActiveNotificationsInteractor) {
+ val content: Flow<PromotedNotificationContentModel?> =
+ activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs ->
+ notifs.firstNotNullOfOrNull { it.promotedContent }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index fe2dabe1ba8a..74809fd8622f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -28,7 +28,7 @@ import com.android.systemui.statusbar.notification.promoted.PromotedNotification
* like the skeleton view on AOD or the status bar chip.
*/
data class PromotedNotificationContentModel(
- val key: String,
+ val identity: Identity,
// for all styles:
val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
@@ -82,7 +82,7 @@ data class PromotedNotificationContentModel(
fun build() =
PromotedNotificationContentModel(
- key = key,
+ identity = Identity(key, style),
skeletonSmallIcon = skeletonSmallIcon,
appName = appName,
subText = subText,
@@ -103,6 +103,8 @@ data class PromotedNotificationContentModel(
)
}
+ data class Identity(val key: String, val style: Style)
+
/** The timestamp associated with a notification, along with the mode used to display it. */
data class When(val time: Long, val mode: Mode) {
/** The mode used to display a notification's `when` value. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt
new file mode 100644
index 000000000000..adfa6a10814d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Identity
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class AODPromotedNotificationViewModel
+@Inject
+constructor(interactor: AODPromotedNotificationInteractor) {
+ private val content: Flow<PromotedNotificationContentModel?> = interactor.content
+ private val identity: Flow<Identity?> = content.mapNonNullsKeepingNulls { it.identity }
+
+ val notification: Flow<PromotedNotificationViewModel?> =
+ identity.distinctUntilChanged().mapNonNullsKeepingNulls { identity ->
+ val updates = interactor.content.filterNotNull().filter { it.identity == identity }
+ PromotedNotificationViewModel(identity, updates)
+ }
+}
+
+private fun <T, R> Flow<T?>.mapNonNullsKeepingNulls(block: (T) -> R): Flow<R?> = map {
+ it?.let(block)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt
new file mode 100644
index 000000000000..f265e0ff33f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted.ui.viewmodel
+
+import android.graphics.drawable.Icon
+import com.android.internal.widget.NotificationProgressModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class PromotedNotificationViewModel(
+ identity: PromotedNotificationContentModel.Identity,
+ content: Flow<PromotedNotificationContentModel>,
+) {
+ // for all styles:
+
+ val key: String = identity.key
+ val style: Style = identity.style
+
+ val skeletonSmallIcon: Flow<Icon?> = content.map { it.skeletonSmallIcon }
+ val appName: Flow<CharSequence?> = content.map { it.appName }
+ val subText: Flow<CharSequence?> = content.map { it.subText }
+
+ private val time: Flow<When?> = content.map { it.time }
+ val whenTime: Flow<Long?> = time.map { it?.time }
+ val whenMode: Flow<When.Mode?> = time.map { it?.mode }
+
+ val lastAudiblyAlertedMs: Flow<Long> = content.map { it.lastAudiblyAlertedMs }
+ val profileBadgeResId: Flow<Int?> = content.map { it.profileBadgeResId }
+ val title: Flow<CharSequence?> = content.map { it.title }
+ val text: Flow<CharSequence?> = content.map { it.text }
+ val skeletonLargeIcon: Flow<Icon?> = content.map { it.skeletonLargeIcon }
+
+ // for CallStyle:
+ val personIcon: Flow<Icon?> = content.map { it.personIcon }
+ val personName: Flow<CharSequence?> = content.map { it.personName }
+ val verificationIcon: Flow<Icon?> = content.map { it.verificationIcon }
+ val verificationText: Flow<CharSequence?> = content.map { it.verificationText }
+
+ // for ProgressStyle:
+ val progress: Flow<NotificationProgressModel?> = content.map { it.progress }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 6e05e8e8b80e..70e27a981b49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
@@ -25,6 +26,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationConten
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.Notification.MessagingStyle;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
@@ -161,9 +163,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
entry,
mConversationProcessor,
row,
- bindParams.isMinimized,
- bindParams.usesIncreasedHeight,
- bindParams.usesIncreasedHeadsUpHeight,
+ bindParams,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
/* isMediaFlagEnabled = */ mIsMediaInQS,
@@ -187,13 +187,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
Notification.Builder builder,
+ Context systemUiContext,
Context packageContext,
SmartReplyStateInflater smartRepliesInflater) {
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
- bindParams.isMinimized,
- bindParams.usesIncreasedHeight,
- bindParams.usesIncreasedHeadsUpHeight,
+ bindParams,
+ systemUiContext,
packageContext,
row,
mNotifLayoutInflaterFactoryProvider,
@@ -203,18 +203,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
boolean isConversation = entry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation && (AsyncHybridViewInflation.isEnabled()
+ || LockscreenOtpRedaction.isSingleLineViewEnabled())) {
+ messagingStyle = mConversationProcessor
+ .processNotification(entry, builder, mLogger);
+ }
if (AsyncHybridViewInflation.isEnabled()) {
- Notification.MessagingStyle messagingStyle = null;
- if (isConversation) {
- messagingStyle = mConversationProcessor
- .processNotification(entry, builder, mLogger);
- }
SingleLineViewModel viewModel = SingleLineViewInflater
.inflateSingleLineViewModel(
entry.getSbn().getNotification(),
messagingStyle,
builder,
- row.getContext()
+ row.getContext(),
+ false
);
// If the messagingStyle is null, we want to inflate the normal view
isConversation = viewModel.isConversation();
@@ -228,11 +230,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mLogger
);
}
-
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
- result.mPublicInflatedSingleLineViewModel =
- SingleLineViewInflater.inflateRedactedSingleLineViewModel(row.getContext(),
- isConversation);
+ if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ entry.getSbn().getNotification(),
+ messagingStyle,
+ builder,
+ row.getContext(),
+ true);
+ } else {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+ row.getContext(),
+ isConversation
+ );
+ }
result.mPublicInflatedSingleLineView =
SingleLineViewInflater.inflatePublicSingleLineView(
isConversation,
@@ -411,8 +424,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
- Notification.Builder builder, boolean isMinimized, boolean usesIncreasedHeight,
- boolean usesIncreasedHeadsUpHeight, Context packageContext,
+ Notification.Builder builder, BindParams bindParams, Context systemUiContext,
+ Context packageContext,
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
@@ -423,13 +436,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
- result.newContentView = createContentView(builder, isMinimized,
- usesIncreasedHeight);
+ result.newContentView = createContentView(builder, bindParams.isMinimized,
+ bindParams.usesIncreasedHeight);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
- result.newExpandedView = createExpandedView(builder, isMinimized);
+ result.newExpandedView = createExpandedView(builder, bindParams.isMinimized);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
@@ -439,13 +452,20 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result.newHeadsUpView = builder.createCompactHeadsUpContentView();
} else {
result.newHeadsUpView = builder.createHeadsUpContentView(
- usesIncreasedHeadsUpHeight);
+ bindParams.usesIncreasedHeadsUpHeight);
}
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
- result.newPublicView = builder.makePublicContentView(isMinimized);
+ if (LockscreenOtpRedaction.isEnabled()
+ && bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ result.newPublicView = createSensitiveContentMessageNotification(
+ row.getEntry().getSbn().getNotification(), builder.getStyle(),
+ systemUiContext, packageContext).createContentView(true);
+ } else {
+ result.newPublicView = builder.makePublicContentView(bindParams.isMinimized);
+ }
}
if (AsyncGroupHeaderViewInflation.isEnabled()) {
@@ -473,6 +493,42 @@ public class NotificationContentInflater implements NotificationRowContentBinder
});
}
+ private static Notification.Builder createSensitiveContentMessageNotification(
+ Notification original,
+ Notification.Style originalStyle,
+ Context systemUiContext,
+ Context packageContext) {
+ Notification.Builder redacted =
+ new Notification.Builder(packageContext, original.getChannelId());
+ redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE));
+ CharSequence redactedMessage = systemUiContext.getString(
+ R.string.redacted_notification_single_line_text
+ );
+
+ if (originalStyle instanceof MessagingStyle oldStyle) {
+ MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser());
+ newStyle.setConversationTitle(oldStyle.getConversationTitle());
+ newStyle.setGroupConversation(false);
+ newStyle.setConversationType(oldStyle.getConversationType());
+ newStyle.setShortcutIcon(oldStyle.getShortcutIcon());
+ newStyle.setBuilder(redacted);
+ MessagingStyle.Message latestMessage =
+ MessagingStyle.findLatestIncomingMessage(oldStyle.getMessages());
+ if (latestMessage != null) {
+ MessagingStyle.Message newMessage = new MessagingStyle.Message(redactedMessage,
+ latestMessage.getTimestamp(), latestMessage.getSenderPerson());
+ newStyle.addMessage(newMessage);
+ }
+ redacted.setStyle(newStyle);
+ } else {
+ redacted.setContentText(redactedMessage);
+ }
+ redacted.setLargeIcon(original.getLargeIcon());
+ redacted.setSmallIcon(original.getSmallIcon());
+ return redacted;
+ }
+
+
private static void setNotifsViewsInflaterFactory(InflationProgress result,
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
@@ -921,7 +977,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
logger.logAsyncTaskProgress(entry, "finishing");
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent);
+ entry.setPromotedNotificationContentModel(result.mPromotedContent);
}
boolean setRepliesAndActions = true;
@@ -1118,10 +1174,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final NotificationEntry mEntry;
private final Context mContext;
private final boolean mInflateSynchronously;
- private final boolean mIsMinimized;
- private final boolean mUsesIncreasedHeight;
+ private final BindParams mBindParams;
private final InflationCallback mCallback;
- private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
private final Executor mInflationExecutor;
@@ -1145,9 +1199,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationEntry entry,
ConversationNotificationProcessor conversationProcessor,
ExpandableNotificationRow row,
- boolean isMinimized,
- boolean usesIncreasedHeight,
- boolean usesIncreasedHeadsUpHeight,
+ BindParams bindParams,
InflationCallback callback,
RemoteViews.InteractionHandler remoteViewClickHandler,
boolean isMediaFlagEnabled,
@@ -1164,9 +1216,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewCache = cache;
mSmartRepliesInflater = smartRepliesInflater;
mContext = mRow.getContext();
- mIsMinimized = isMinimized;
- mUsesIncreasedHeight = usesIncreasedHeight;
- mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
+ mBindParams = bindParams;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
mConversationProcessor = conversationProcessor;
@@ -1236,8 +1286,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mEntry, recoveredBuilder, mLogger);
}
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
- recoveredBuilder, mIsMinimized, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+ recoveredBuilder, mBindParams, mContext, packageContext, mRow,
mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, mLogger);
mLogger.logAsyncTaskProgress(mEntry,
@@ -1264,7 +1313,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mEntry.getSbn().getNotification(),
messagingStyle,
recoveredBuilder,
- mContext
+ mContext,
+ false
);
result.mInflatedSingleLineView =
SingleLineViewInflater.inflatePrivateSingleLineView(
@@ -1277,9 +1327,22 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
- result.mPublicInflatedSingleLineViewModel =
- SingleLineViewInflater.inflateRedactedSingleLineViewModel(mContext,
- isConversation);
+ if (mBindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext,
+ true
+ );
+ } else {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+ mContext,
+ isConversation
+ );
+ }
result.mPublicInflatedSingleLineView =
SingleLineViewInflater.inflatePublicSingleLineView(
isConversation,
@@ -1292,10 +1355,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if (PromotedNotificationContentModel.featureFlagEnabled()) {
mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content");
- result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor
- .extractContent(mEntry, recoveredBuilder);
+ final PromotedNotificationContentModel promotedContent =
+ mPromotedNotificationContentExtractor.extractContent(mEntry,
+ recoveredBuilder);
mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: "
- + result.mExtractedPromotedNotificationContent);
+ + promotedContent);
+
+ result.mPromotedContent = promotedContent;
}
mLogger.logAsyncTaskProgress(mEntry,
@@ -1317,7 +1383,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mCancellationSignal = apply(
mInflationExecutor,
mInflateSynchronously,
- mIsMinimized,
+ mBindParams.isMinimized,
result,
mReInflateFlags,
mRemoteViewCache,
@@ -1399,7 +1465,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@VisibleForTesting
static class InflationProgress {
- PromotedNotificationContentModel mExtractedPromotedNotificationContent;
+ PromotedNotificationContentModel mPromotedContent;
private RemoteViews newContentView;
private RemoteViews newHeadsUpView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 07384afe2d2e..1cef8791e0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -141,20 +143,33 @@ public interface NotificationRowContentBinder {
*/
class BindParams {
+ public BindParams(boolean minimized, boolean increasedHeight,
+ boolean increasedHeadsUpHeight, int redaction) {
+ isMinimized = minimized;
+ usesIncreasedHeight = increasedHeight;
+ usesIncreasedHeadsUpHeight = increasedHeadsUpHeight;
+ redactionType = redaction;
+ }
+
/**
* Bind a minimized version of the content views.
*/
- public boolean isMinimized;
+ public final boolean isMinimized;
/**
* Use increased height when binding contracted view.
*/
- public boolean usesIncreasedHeight;
+ public final boolean usesIncreasedHeight;
/**
* Use increased height when binding heads up views.
*/
- public boolean usesIncreasedHeadsUpHeight;
+ public final boolean usesIncreasedHeadsUpHeight;
+
+ /**
+ * Controls the type of public view to show, if a public view is requested
+ */
+ public final @RedactionType int redactionType;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c7d80e9d03ce..c619b17f1ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
-import android.app.Flags
import android.app.Notification
+import android.app.Notification.MessagingStyle
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.ApplicationInfo
@@ -43,6 +43,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.NotifInflation
import com.android.systemui.res.R
import com.android.systemui.statusbar.InflationTask
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
@@ -143,9 +144,7 @@ constructor(
entry,
conversationProcessor,
row,
- bindParams.isMinimized,
- bindParams.usesIncreasedHeight,
- bindParams.usesIncreasedHeadsUpHeight,
+ bindParams,
callback,
remoteInputManager.remoteViewsOnClickHandler,
/* isMediaFlagEnabled = */ smartReplyStateInflater,
@@ -179,10 +178,8 @@ constructor(
reInflateFlags = reInflateFlags,
entry = entry,
builder = builder,
- isMinimized = bindParams.isMinimized,
- usesIncreasedHeight = bindParams.usesIncreasedHeight,
- usesIncreasedHeadsUpHeight = bindParams.usesIncreasedHeadsUpHeight,
- systemUIContext = systemUIContext,
+ bindParams,
+ systemUiContext = systemUIContext,
packageContext = packageContext,
row = row,
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
@@ -371,9 +368,7 @@ constructor(
private val entry: NotificationEntry,
private val conversationProcessor: ConversationNotificationProcessor,
private val row: ExpandableNotificationRow,
- private val isMinimized: Boolean,
- private val usesIncreasedHeight: Boolean,
- private val usesIncreasedHeadsUpHeight: Boolean,
+ private val bindParams: BindParams,
private val callback: InflationCallback?,
private val remoteViewClickHandler: InteractionHandler?,
private val smartRepliesInflater: SmartReplyStateInflater,
@@ -441,10 +436,8 @@ constructor(
reInflateFlags = reInflateFlags,
entry = entry,
builder = recoveredBuilder,
- isMinimized = isMinimized,
- usesIncreasedHeight = usesIncreasedHeight,
- usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight,
- systemUIContext = context,
+ bindParams = bindParams,
+ systemUiContext = context,
packageContext = packageContext,
row = row,
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
@@ -514,7 +507,7 @@ constructor(
apply(
inflationExecutor,
inflateSynchronously,
- isMinimized,
+ bindParams.isMinimized,
progress,
reInflateFlags,
remoteViewCache,
@@ -591,7 +584,7 @@ constructor(
@VisibleForTesting val packageContext: Context,
val remoteViews: NewRemoteViews,
val contentModel: NotificationContentModel,
- val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModel?,
) {
var inflatedContentView: View? = null
@@ -671,10 +664,8 @@ constructor(
@InflationFlag reInflateFlags: Int,
entry: NotificationEntry,
builder: Notification.Builder,
- isMinimized: Boolean,
- usesIncreasedHeight: Boolean,
- usesIncreasedHeadsUpHeight: Boolean,
- systemUIContext: Context,
+ bindParams: BindParams,
+ systemUiContext: Context,
packageContext: Context,
row: ExpandableNotificationRow,
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
@@ -683,16 +674,15 @@ constructor(
promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
logger: NotificationRowContentBinderLogger,
): InflationProgress {
- val promoted =
+ val promotedContent =
if (PromotedNotificationContentModel.featureFlagEnabled()) {
logger.logAsyncTaskProgress(entry, "extracting promoted notification content")
- val extracted =
- promotedNotificationContentExtractor.extractContent(entry, builder)
- logger.logAsyncTaskProgress(
- entry,
- "extracted promoted notification content: {extracted}",
- )
- extracted
+ promotedNotificationContentExtractor.extractContent(entry, builder).also {
+ logger.logAsyncTaskProgress(
+ entry,
+ "extracted promoted notification content: $it",
+ )
+ }
} else {
null
}
@@ -707,9 +697,10 @@ constructor(
createRemoteViews(
reInflateFlags = reInflateFlags,
builder = builder,
- isMinimized = isMinimized,
- usesIncreasedHeight = usesIncreasedHeight,
- usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight,
+ bindParams = bindParams,
+ entry = entry,
+ systemUiContext = systemUiContext,
+ packageContext = packageContext,
row = row,
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
@@ -726,7 +717,8 @@ constructor(
notification = entry.sbn.notification,
messagingStyle = messagingStyle,
builder = builder,
- systemUiContext = systemUIContext,
+ systemUiContext = systemUiContext,
+ redactText = false,
)
} else null
@@ -736,10 +728,20 @@ constructor(
reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
) {
logger.logAsyncTaskProgress(entry, "inflating public single line view model")
- SingleLineViewInflater.inflateRedactedSingleLineViewModel(
- systemUIContext,
- entry.ranking.isConversation,
- )
+ if (bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = entry.sbn.notification,
+ messagingStyle = messagingStyle,
+ builder = builder,
+ systemUiContext = systemUiContext,
+ redactText = true,
+ )
+ } else {
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+ systemUiContext,
+ entry.ranking.isConversation,
+ )
+ }
} else null
val headsUpStatusBarModel =
@@ -759,16 +761,54 @@ constructor(
packageContext = packageContext,
remoteViews = remoteViews,
contentModel = contentModel,
- extractedPromotedNotificationContentModel = promoted,
+ promotedContent = promotedContent,
)
}
+ private fun createSensitiveContentMessageNotification(
+ original: Notification,
+ originalStyle: Notification.Style?,
+ sysUiContext: Context,
+ packageContext: Context,
+ ): Notification.Builder {
+ val redacted = Notification.Builder(packageContext, original.channelId)
+ redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE))
+ val redactedMessage =
+ sysUiContext.getString(R.string.redacted_notification_single_line_text)
+
+ if (originalStyle is MessagingStyle) {
+ val newStyle = MessagingStyle(originalStyle.user)
+ newStyle.conversationTitle = originalStyle.conversationTitle
+ newStyle.isGroupConversation = false
+ newStyle.conversationType = originalStyle.conversationType
+ newStyle.shortcutIcon = originalStyle.shortcutIcon
+ newStyle.setBuilder(redacted)
+ val latestMessage = MessagingStyle.findLatestIncomingMessage(originalStyle.messages)
+ if (latestMessage != null) {
+ val newMessage =
+ MessagingStyle.Message(
+ redactedMessage,
+ latestMessage.timestamp,
+ latestMessage.senderPerson,
+ )
+ newStyle.addMessage(newMessage)
+ }
+ redacted.style = newStyle
+ } else {
+ redacted.setContentText(redactedMessage)
+ }
+ redacted.setLargeIcon(original.getLargeIcon())
+ redacted.setSmallIcon(original.smallIcon)
+ return redacted
+ }
+
private fun createRemoteViews(
@InflationFlag reInflateFlags: Int,
builder: Notification.Builder,
- isMinimized: Boolean,
- usesIncreasedHeight: Boolean,
- usesIncreasedHeadsUpHeight: Boolean,
+ bindParams: BindParams,
+ entry: NotificationEntry,
+ systemUiContext: Context,
+ packageContext: Context,
row: ExpandableNotificationRow,
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
@@ -782,7 +822,11 @@ constructor(
entryForLogging,
"creating contracted remote view",
)
- createContentView(builder, isMinimized, usesIncreasedHeight)
+ createContentView(
+ builder,
+ bindParams.isMinimized,
+ bindParams.usesIncreasedHeight,
+ )
} else null
val expanded =
if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
@@ -790,7 +834,7 @@ constructor(
entryForLogging,
"creating expanded remote view",
)
- createExpandedView(builder, isMinimized)
+ createExpandedView(builder, bindParams.isMinimized)
} else null
val headsUp =
if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
@@ -802,13 +846,26 @@ constructor(
if (isHeadsUpCompact) {
builder.createCompactHeadsUpContentView()
} else {
- builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight)
+ builder.createHeadsUpContentView(bindParams.usesIncreasedHeadsUpHeight)
}
} else null
val public =
if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) {
logger.logAsyncTaskProgress(entryForLogging, "creating public remote view")
- builder.makePublicContentView(isMinimized)
+ if (
+ LockscreenOtpRedaction.isEnabled &&
+ bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT
+ ) {
+ createSensitiveContentMessageNotification(
+ entry.sbn.notification,
+ builder.style,
+ systemUiContext,
+ packageContext,
+ )
+ .createContentView(bindParams.usesIncreasedHeight)
+ } else {
+ builder.makePublicContentView(bindParams.isMinimized)
+ }
} else null
val normalGroupHeader =
if (
@@ -1420,8 +1477,7 @@ constructor(
entry.setContentModel(result.contentModel)
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.promotedNotificationContentModel =
- result.extractedPromotedNotificationContentModel
+ entry.promotedNotificationContentModel = result.promotedContent
}
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index 427fb66ca2d0..bc44cb0e1074 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE;
+import static com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
@@ -31,6 +33,7 @@ public final class RowContentBindParams {
private boolean mUseIncreasedHeadsUpHeight;
private boolean mViewsNeedReinflation;
private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS;
+ private @RedactionType int mRedactionType = REDACTION_TYPE_NONE;
/**
* Content views that are out of date and need to be rebound.
@@ -58,6 +61,20 @@ public final class RowContentBindParams {
}
/**
+ * @return What type of redaction should be used by the public view (if requested)
+ */
+ public @RedactionType int getRedactionType() {
+ return mRedactionType;
+ }
+
+ /**
+ * Set the redaction type, which controls what sort of public view is shown.
+ */
+ public void setRedactionType(@RedactionType int redactionType) {
+ mRedactionType = redactionType;
+ }
+
+ /**
* Set whether content should use an increased height version of its contracted view.
*/
public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 89fcda949b5b..53f74161e7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -72,10 +72,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
// Bind/unbind with parameters
mBinder.unbindContent(entry, row, contentToUnbind);
- BindParams bindParams = new BindParams();
- bindParams.isMinimized = params.useMinimized();
- bindParams.usesIncreasedHeight = params.useIncreasedHeight();
- bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight();
+ BindParams bindParams = new BindParams(params.useMinimized(), params.useIncreasedHeight(),
+ params.useIncreasedHeadsUpHeight(), params.getRedactionType());
boolean forceInflate = params.needsReinflation();
InflationCallback inflationCallback = new InflationCallback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index e702f10d7f50..fe2803bfc5d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -51,6 +51,7 @@ internal object SingleLineViewInflater {
* notification, not for legacy messaging notifications
* @param builder the recovered Notification Builder
* @param systemUiContext the context of Android System UI
+ * @param redactText indicates if the text needs to be redacted
* @return the inflated SingleLineViewModel
*/
@JvmStatic
@@ -59,13 +60,21 @@ internal object SingleLineViewInflater {
messagingStyle: MessagingStyle?,
builder: Notification.Builder,
systemUiContext: Context,
+ redactText: Boolean,
): SingleLineViewModel {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
return SingleLineViewModel(null, null, null)
}
peopleHelper.init(systemUiContext)
var titleText = HybridGroupManager.resolveTitle(notification)
- var contentText = HybridGroupManager.resolveText(notification)
+ var contentText =
+ if (redactText) {
+ systemUiContext.getString(
+ com.android.systemui.res.R.string.redacted_notification_single_line_text
+ )
+ } else {
+ HybridGroupManager.resolveText(notification)
+ }
if (messagingStyle == null) {
return SingleLineViewModel(
@@ -81,7 +90,7 @@ internal object SingleLineViewInflater {
if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
titleText = conversationTextData.conversationTitle
}
- if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+ if (!redactText && conversationTextData?.conversationText?.isNotEmpty() == true) {
contentText = conversationTextData.conversationText
}
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 7c9d850eaf07..38a70359e816 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
@@ -3729,14 +3729,6 @@ public class NotificationStackScrollLayout
// Only when scene container is enabled, mark that we are being dragged so that we start
// dispatching the rest of the gesture to scene container.
- void startOverscrollAfterExpanding() {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- getExpandHelper().finishExpanding();
- setIsBeingDragged(true);
- }
-
- // Only when scene container is enabled, mark that we are being dragged so that we start
- // dispatching the rest of the gesture to scene container.
void startDraggingOnHun() {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
setIsBeingDragged(true);
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 245b1d29fb79..a33a9ed2df75 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
@@ -2203,11 +2203,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
expandingNotification = mView.isExpandingNotification();
if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore
&& !mView.getDisallowScrollingInThisMotion()) {
- // We need to dispatch the overscroll differently when Scene Container is on,
- // since NSSL no longer controls its own scroll.
+ // Finish expansion here, as this gesture will be marked to be sent to
+ // scene container
if (SceneContainerFlag.isEnabled() && !isCancelOrUp) {
- mView.startOverscrollAfterExpanding();
- return true;
+ expandHelper.finishExpanding();
} else {
mView.dispatchDownEventToScroller(ev);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
index 42acd7bcdc8a..705845ff984c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -75,7 +75,7 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) :
constraintSet.apply {
if (SceneContainerFlag.isEnabled) {
when (horizontalPosition) {
- is HorizontalPosition.FloatAtEnd ->
+ is HorizontalPosition.FloatAtStart ->
constrainWidth(nsslId, horizontalPosition.width)
is HorizontalPosition.MiddleToEdge ->
setGuidelinePercent(R.id.nssl_guideline, horizontalPosition.ratio)
@@ -83,13 +83,13 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) :
}
}
+ connect(nsslId, START, startConstraintId, START, marginStart)
if (
!SceneContainerFlag.isEnabled ||
- horizontalPosition !is HorizontalPosition.FloatAtEnd
+ horizontalPosition !is HorizontalPosition.FloatAtStart
) {
- connect(nsslId, START, startConstraintId, START, marginStart)
+ connect(nsslId, END, PARENT_ID, END, marginEnd)
}
- connect(nsslId, END, PARENT_ID, END, marginEnd)
connect(nsslId, BOTTOM, PARENT_ID, BOTTOM, marginBottom)
connect(nsslId, TOP, PARENT_ID, TOP, marginTop)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index b81c71ebe19b..fc8c70fb8e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -247,7 +247,7 @@ constructor(
Split -> HorizontalPosition.MiddleToEdge(ratio = 0.5f)
Dual ->
if (isShadeLayoutWide) {
- HorizontalPosition.FloatAtEnd(
+ HorizontalPosition.FloatAtStart(
width = getDimensionPixelSize(R.dimen.shade_panel_width)
)
} else {
@@ -830,10 +830,10 @@ constructor(
data class MiddleToEdge(val ratio: Float = 0.5f) : HorizontalPosition
/**
- * The container has a fixed [width] and is aligned to the end of the screen. In this
- * layout, the start edge of the container is floating, i.e. unconstrained.
+ * The container has a fixed [width] and is aligned to the start of the screen. In this
+ * layout, the end edge of the container is floating, i.e. unconstrained.
*/
- data class FloatAtEnd(val width: Int) : HorizontalPosition
+ data class FloatAtStart(val width: Int) : HorizontalPosition
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7bea4800f7fc..6dc25aa5144f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -21,9 +21,10 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
-import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
@@ -207,6 +208,7 @@ import com.android.systemui.statusbar.data.repository.StatusBarModeRepositorySto
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -221,7 +223,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -2514,12 +2515,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
* should update only the status bar components.
*/
private void setBouncerShowingForStatusBarComponents(boolean bouncerShowing) {
- int importance = bouncerShowing
- ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
if (!StatusBarConnectedDisplays.isEnabled() && mPhoneStatusBarViewController != null) {
+ int importance = bouncerShowing
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO;
mPhoneStatusBarViewController.setImportantForAccessibility(importance);
}
+ int importance = bouncerShowing
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_NO;
mShadeSurface.setImportantForAccessibility(importance);
mShadeSurface.setBouncerShowing(bouncerShowing);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index f37bc6b2d4fb..4d1d64ea24c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -37,10 +37,12 @@ import androidx.annotation.NonNull;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeViewController;
@@ -59,6 +61,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
@@ -69,7 +72,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.Set;
@@ -102,6 +104,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
+ private final DeviceUnlockedInteractor mDeviceUnlockedInteractor;
private final QuickSettingsController mQsController;
protected boolean mVrMode;
@@ -133,7 +136,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
NotificationRemoteInputManager remoteInputManager,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
- NotificationListContainer notificationListContainer) {
+ NotificationListContainer notificationListContainer,
+ DeviceUnlockedInteractor deviceUnlockedInteractor) {
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
@@ -160,6 +164,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mNotifListContainer = notificationListContainer;
+ mDeviceUnlockedInteractor = deviceUnlockedInteractor;
IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
@@ -246,16 +251,27 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
if (nowExpanded) {
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
- } else if (clickedEntry.isSensitive().getValue()
- && mDynamicPrivacyController.isInLockedDownShade()) {
+ mShadeTransitionController.goToLockedShade(
+ clickedEntry.getRow(), /* needsQSAnimation = */ true);
+ } else if (clickedEntry.isSensitive().getValue() && isInLockedDownShade()) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+ // launch the bouncer if the device is locked
mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */
, null /* cancelRunnable */, false /* afterKeyguardGone */);
}
}
}
+ /** @return true if the Shade is shown over the Lockscreen, and the device is locked */
+ private boolean isInLockedDownShade() {
+ if (SceneContainerFlag.isEnabled()) {
+ return mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ && !mDeviceUnlockedInteractor.getDeviceUnlockStatus().getValue().isUnlocked();
+ } else {
+ return mDynamicPrivacyController.isInLockedDownShade();
+ }
+ }
+
@Override
public boolean isDeviceInVrMode() {
return mVrMode;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 78954dea27ba..8b60ee56d5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -192,7 +192,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
- mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
+ mUserSwitchDialogController.showDialog(
Expandable.fromView(mUserAvatarViewWithBackground));
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index a1d5cbea62f9..9ff0d18f0e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dialog.ui.composable.AlertDialogContent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -67,6 +68,7 @@ constructor(
private val viewModel: Provider<ModesDialogViewModel>,
private val dialogEventLogger: ModesDialogEventLogger,
@Main private val mainCoroutineContext: CoroutineContext,
+ private val shadeDisplayContextRepository: ShadeDialogContextInteractor,
) : SystemUIDialog.Delegate {
// NOTE: This should only be accessed/written from the main thread.
@VisibleForTesting var currentDialog: ComponentSystemUIDialog? = null
@@ -78,7 +80,10 @@ constructor(
currentDialog?.dismiss()
}
- currentDialog = sysuiDialogFactory.create { ModesDialogContent(it) }
+ currentDialog =
+ sysuiDialogFactory.create(context = shadeDisplayContextRepository.context) {
+ ModesDialogContent(it)
+ }
currentDialog
?.lifecycle
?.addObserver(
@@ -106,9 +111,8 @@ constructor(
modifier =
Modifier.semantics {
testTagsAsResourceId = true
- paneTitle = dialog.context.getString(
- R.string.accessibility_desc_quick_settings
- )
+ paneTitle =
+ dialog.context.getString(R.string.accessibility_desc_quick_settings)
},
title = {
Text(
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index a382cf921152..e08114f6c3cd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -21,10 +21,8 @@ import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.PowerManager
import android.provider.Settings
-import androidx.core.view.OneShotPreDrawListener
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.ToAodFoldTransitionInteractor
@@ -125,11 +123,7 @@ constructor(
private val shadeFoldAnimator: ShadeFoldAnimator
get() {
- return if (MigrateClocksToBlueprint.isEnabled) {
- foldTransitionInteractor.get().foldAnimator
- } else {
- shadeViewController.shadeFoldAnimator
- }
+ return foldTransitionInteractor.get().foldAnimator
}
private fun setAnimationState(playing: Boolean) {
@@ -164,15 +158,7 @@ constructor(
setAnimationState(playing = true)
shadeFoldAnimator.prepareFoldToAodAnimation()
- // We don't need to wait for the scrim as it is already displayed
- // but we should wait for the initial animation preparations to be drawn
- // (setting initial alpha/translation)
- // TODO(b/254878364): remove this call to NPVC.getView()
- if (!MigrateClocksToBlueprint.isEnabled) {
- shadeFoldAnimator.view?.let { OneShotPreDrawListener.add(it, onReady) }
- } else {
- onReady.run()
- }
+ onReady.run()
} else {
// No animation, call ready callback immediately
onReady.run()
@@ -252,7 +238,7 @@ constructor(
if (isFolded) {
foldToAodLatencyTracker.onFolded()
}
- }
+ },
)
/**
@@ -272,6 +258,7 @@ constructor(
latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
}
}
+
/**
* Called once the Fold -> AOD animation is started.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 102fcc0c59f2..e4b2dc25e411 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -18,7 +18,6 @@
package com.android.systemui.user.ui.dialog
import android.app.Dialog
-import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.users.UserCreatingDialog
@@ -32,6 +31,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.user.UserSwitchFullscreenDialog
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -48,7 +48,6 @@ import com.android.app.tracing.coroutines.launchTraced as launch
class UserSwitcherDialogCoordinator
@Inject
constructor(
- @Application private val context: Lazy<Context>,
@Application private val applicationScope: Lazy<CoroutineScope>,
private val falsingManager: Lazy<FalsingManager>,
private val broadcastSender: Lazy<BroadcastSender>,
@@ -59,6 +58,7 @@ constructor(
private val activityStarter: Lazy<ActivityStarter>,
private val falsingCollector: Lazy<FalsingCollector>,
private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
+ private val shadeDialogContextInteractor: Lazy<ShadeDialogContextInteractor>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -71,12 +71,13 @@ constructor(
private fun startHandlingDialogShowRequests() {
applicationScope.get().launch {
interactor.get().dialogShowRequests.filterNotNull().collect { request ->
+ val context = shadeDialogContextInteractor.get().context
val (dialog, dialogCuj) =
when (request) {
is ShowDialogRequestModel.ShowAddUserDialog ->
Pair(
AddUserDialog(
- context = context.get(),
+ context = context,
userHandle = request.userHandle,
isKeyguardShowing = request.isKeyguardShowing,
showEphemeralMessage = request.showEphemeralMessage,
@@ -92,7 +93,7 @@ constructor(
is ShowDialogRequestModel.ShowUserCreationDialog ->
Pair(
UserCreatingDialog(
- context.get(),
+ context,
request.isGuest,
),
null,
@@ -100,7 +101,7 @@ constructor(
is ShowDialogRequestModel.ShowExitGuestDialog ->
Pair(
ExitGuestDialog(
- context = context.get(),
+ context = context,
guestUserId = request.guestUserId,
isGuestEphemeral = request.isGuestEphemeral,
targetUserId = request.targetUserId,
@@ -117,7 +118,7 @@ constructor(
is ShowDialogRequestModel.ShowUserSwitcherDialog ->
Pair(
UserSwitchDialog(
- context = context.get(),
+ context = context,
adapter = userDetailAdapterProvider.get(),
uiEventLogger = eventLogger.get(),
falsingManager = falsingManager.get(),
@@ -132,7 +133,7 @@ constructor(
is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
Pair(
UserSwitchFullscreenDialog(
- context = context.get(),
+ context = context,
falsingCollector = falsingCollector.get(),
userSwitcherViewModel = userSwitcherViewModel.get(),
),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
index 2e1f82d56fc4..70e342f3eefb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
@@ -17,45 +17,28 @@
package com.android.systemui.volume.dialog.settings.ui.binder
import android.view.View
-import com.android.systemui.lifecycle.WindowLifecycleState
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
-import com.android.systemui.lifecycle.viewModel
+import android.widget.ImageButton
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.settings.ui.viewmodel.VolumeDialogSettingsButtonViewModel
import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@VolumeDialogScope
class VolumeDialogSettingsButtonViewBinder
@Inject
-constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Factory) {
+constructor(private val viewModel: VolumeDialogSettingsButtonViewModel) {
- fun bind(view: View) {
- with(view) {
- val button = requireViewById<View>(R.id.volume_dialog_settings)
- repeatWhenAttached {
- viewModel(
- traceName = "VolumeDialogViewBinder",
- minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelFactory.create() },
- ) { viewModel ->
- setSnapshotBinding {
- viewModel.isVisible
- .onEach { isVisible ->
- visibility = if (isVisible) View.VISIBLE else View.GONE
- }
- .launchIn(this)
+ fun CoroutineScope.bind(view: View) {
+ val button = view.requireViewById<ImageButton>(R.id.volume_dialog_settings)
+ viewModel.isVisible
+ .onEach { isVisible -> button.visibility = if (isVisible) View.VISIBLE else View.GONE }
+ .launchIn(this)
- button.setOnClickListener { viewModel.onButtonClicked() }
- }
+ viewModel.icon.onEach { button.setImageDrawable(it) }.launchIn(this)
- awaitCancellation()
- }
- }
- }
+ button.setOnClickListener { viewModel.onButtonClicked() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
index 015d773b2c02..03442dbcde66 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/viewmodel/VolumeDialogSettingsButtonViewModel.kt
@@ -14,27 +14,206 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.volume.dialog.settings.ui.viewmodel
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.media.session.PlaybackState
+import androidx.annotation.ColorInt
+import com.airbnb.lottie.LottieComposition
+import com.airbnb.lottie.LottieCompositionFactory
+import com.airbnb.lottie.LottieDrawable
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.SimpleColorFilter
+import com.airbnb.lottie.model.KeyPath
+import com.airbnb.lottie.value.LottieValueCallback
+import com.android.internal.R as internalR
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.lottie.await
+import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.settings.domain.VolumeDialogSettingsButtonInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.shared.model.filterData
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
+import kotlinx.coroutines.suspendCancellableCoroutine
class VolumeDialogSettingsButtonViewModel
-@AssistedInject
-constructor(private val interactor: VolumeDialogSettingsButtonInteractor) {
+@Inject
+constructor(
+ @Application private val context: Context,
+ @UiBackground private val uiBgCoroutineContext: CoroutineContext,
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ mediaOutputInteractor: MediaOutputInteractor,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+ private val interactor: VolumeDialogSettingsButtonInteractor,
+) {
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ private val drawables: Flow<Drawables> =
+ flow {
+ val color = context.getColor(internalR.color.materialColorPrimary)
+ emit(
+ Drawables(
+ start =
+ LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_in)
+ .await()
+ .toDrawable { setColor(color) },
+ playing =
+ LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_playing)
+ .await()
+ .toDrawable {
+ repeatCount = LottieDrawable.INFINITE
+ repeatMode = LottieDrawable.RESTART
+ setColor(color)
+ },
+ stop =
+ LottieCompositionFactory.fromRawRes(context, R.raw.audio_bars_out)
+ .await()
+ .toDrawable { setColor(color) },
+ idle = context.getDrawable(R.drawable.audio_bars_idle)!!,
+ )
+ )
+ }
+ .buffer()
+ .flowOn(uiBgCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
val isVisible = interactor.isVisible
+ val icon: Flow<Drawable> =
+ mediaOutputInteractor.defaultActiveMediaSession
+ .filterData()
+ .flatMapLatest { session ->
+ if (session == null) {
+ flowOf(null)
+ } else {
+ mediaDeviceSessionInteractor.playbackState(session)
+ }
+ }
+ .runningFold(null) { playbackStates: PlaybackStates?, playbackState: PlaybackState? ->
+ val isCurrentActive = playbackState?.isActive ?: false
+ if (playbackStates != null && isCurrentActive == playbackState?.isActive) {
+ return@runningFold playbackStates
+ }
+ playbackStates?.copy(
+ isPreviousActive = playbackStates.isCurrentActive,
+ isCurrentActive = isCurrentActive,
+ ) ?: PlaybackStates(isPreviousActive = null, isCurrentActive = isCurrentActive)
+ }
+ .filterNotNull()
+ // only apply the most recent state if we wait for the animation.
+ .buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ // distinct again because the changed state might've been dropped by the buffer
+ .distinctUntilChangedBy { it.isCurrentActive }
+ .transform { emitDrawables(it) }
+ .runningFold(null) { previous: Drawable?, current: Drawable ->
+ // wait for the previous animation to finish before starting the new one
+ // this also waits for the current loop of the playing animation to finish
+ (previous as? LottieDrawable)?.awaitFinish()
+ (current as? LottieDrawable)?.start()
+ current
+ }
+ .filterNotNull()
+
+ private suspend fun FlowCollector<Drawable>.emitDrawables(playbackStates: PlaybackStates) {
+ val animations = drawables.first()
+ val stateChanged =
+ playbackStates.isPreviousActive != null &&
+ playbackStates.isPreviousActive != playbackStates.isCurrentActive
+ if (playbackStates.isCurrentActive) {
+ if (stateChanged) {
+ emit(animations.start)
+ }
+ emit(animations.playing)
+ } else {
+ if (stateChanged) {
+ emit(animations.stop)
+ }
+ emit(animations.idle)
+ }
+ }
fun onButtonClicked() {
interactor.onButtonClicked()
}
- @VolumeDialogScope
- @AssistedFactory
- interface Factory {
+ private data class PlaybackStates(val isPreviousActive: Boolean?, val isCurrentActive: Boolean)
+
+ private data class Drawables(
+ val start: LottieDrawable,
+ val playing: LottieDrawable,
+ val stop: LottieDrawable,
+ val idle: Drawable,
+ )
+}
+
+private fun LottieComposition.toDrawable(setup: LottieDrawable.() -> Unit = {}): LottieDrawable =
+ LottieDrawable().also { drawable ->
+ drawable.composition = this
+ drawable.setup()
+ }
- fun create(): VolumeDialogSettingsButtonViewModel
+/** Suspends until current loop of the repeating animation is finished */
+private suspend fun LottieDrawable.awaitFinish() = suspendCancellableCoroutine { continuation ->
+ if (!isRunning) {
+ continuation.resume(Unit)
+ return@suspendCancellableCoroutine
}
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationRepeat(animation: Animator) {
+ continuation.resume(Unit)
+ removeAnimatorListener(this)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resume(Unit)
+ removeAnimatorListener(this)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ continuation.resume(Unit)
+ removeAnimatorListener(this)
+ }
+ }
+ addAnimatorListener(listener)
+ continuation.invokeOnCancellation { removeAnimatorListener(listener) }
+}
+
+/**
+ * Overrides colors of the [LottieDrawable] to a specified [color]
+ *
+ * @see com.airbnb.lottie.LottieAnimationView
+ */
+private fun LottieDrawable.setColor(@ColorInt color: Int) {
+ val callback = LottieValueCallback<ColorFilter>(SimpleColorFilter(color))
+ addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER, callback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index f30524638150..faf06b942cab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -26,7 +26,7 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
-import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
+import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import javax.inject.Inject
@@ -84,5 +84,5 @@ private suspend fun Slider.setValueAnimated(
interpolator = DecelerateInterpolator()
addListener(jankListener)
}
- .awaitAnimation<Float> { value = it }
+ .suspendAnimate<Float> { value = it }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
index 10cf615ce0ce..5f124806dac7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/utils/SuspendAnimators.kt
@@ -22,6 +22,7 @@ import android.animation.ValueAnimator
import android.view.ViewPropertyAnimator
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringAnimation
+import com.airbnb.lottie.LottieDrawable
import kotlin.coroutines.resume
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -66,7 +67,7 @@ suspend fun ViewPropertyAnimator.suspendAnimate(
* is cancelled.
*/
@Suppress("UNCHECKED_CAST")
-suspend fun <T> ValueAnimator.awaitAnimation(onValueChanged: (T) -> Unit) {
+suspend fun <T> ValueAnimator.suspendAnimate(onValueChanged: (T) -> Unit) {
suspendCancellableCoroutine { continuation ->
addListener(
object : AnimatorListenerAdapter() {
@@ -103,6 +104,29 @@ suspend fun SpringAnimation.suspendAnimate(
}
}
+/**
+ * Starts the animation and suspends until it's finished. Cancels the animation if the running
+ * coroutine is cancelled.
+ */
+suspend fun LottieDrawable.suspendAnimate() = suspendCancellableCoroutine { continuation ->
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resumeIfCan(Unit)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ continuation.resumeIfCan(Unit)
+ }
+ }
+ addAnimatorListener(listener)
+ start()
+ continuation.invokeOnCancellation {
+ removeAnimatorListener(listener)
+ stop()
+ }
+}
+
private fun <T> CancellableContinuation<T>.resumeIfCan(value: T) {
if (!isCancelled && !isCompleted) {
resume(value)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index 6e1ebc820b08..12e624cae4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -19,10 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto
import android.media.session.MediaController
import android.media.session.PlaybackState
import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext
/** Allows to observe and change [MediaDeviceSession] state. */
@OptIn(ExperimentalCoroutinesApi::class)
-@VolumePanelScope
+@SysUISingleton
class MediaDeviceSessionInteractor
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index b3848a6d7817..2973e11c365d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -24,12 +24,13 @@ import androidx.annotation.WorkerThread
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.shared.model.filterData
import com.android.systemui.volume.panel.shared.model.wrapInResult
@@ -54,13 +55,13 @@ import kotlinx.coroutines.withContext
/** Provides observable models about the current media session state. */
@OptIn(ExperimentalCoroutinesApi::class)
-@VolumePanelScope
+@SysUISingleton
class MediaOutputInteractor
@Inject
constructor(
private val localMediaRepositoryFactory: LocalMediaRepositoryFactory,
private val packageManager: PackageManager,
- @VolumePanelScope private val coroutineScope: CoroutineScope,
+ @Application private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
mediaControllerRepository: MediaControllerRepository,
private val mediaControllerInteractor: MediaControllerInteractor,
@@ -77,7 +78,7 @@ constructor(
.onStart { emit(activeSessions) }
}
.map { getMediaControllers(it) }
- .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null))
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), MediaControllers(null, null))
/** [MediaDeviceSessions] that contains currently active sessions. */
val activeMediaDeviceSessions: Flow<MediaDeviceSessions> =
@@ -89,7 +90,11 @@ constructor(
)
}
.flowOn(backgroundCoroutineContext)
- .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ MediaDeviceSessions(null, null),
+ )
/** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> =
@@ -104,7 +109,7 @@ constructor(
}
.wrapInResult()
.flowOn(backgroundCoroutineContext)
- .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), Result.Loading())
private val localMediaRepository: Flow<LocalMediaRepository> =
defaultActiveMediaSession
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index a41725f754df..4abbbacd800b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -25,7 +25,6 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.Flags
@@ -299,20 +298,6 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun keyguardCallback_visibilityChanged_clockDozeCalled() =
- runBlocking(IMMEDIATE) {
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(capture(captor))
-
- captor.value.onKeyguardVisibilityChanged(true)
- verify(animations, never()).doze(0f)
-
- captor.value.onKeyguardVisibilityChanged(false)
- verify(animations, times(2)).doze(0f)
- }
-
- @Test
fun keyguardCallback_timeFormat_clockNotified() =
runBlocking(IMMEDIATE) {
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
@@ -344,19 +329,6 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun keyguardCallback_verifyKeyguardChanged() =
- runBlocking(IMMEDIATE) {
- val job = underTest.listenForDozeAmount(this)
- repository.setDozeAmount(0.4f)
-
- yield()
-
- verify(animations, times(2)).doze(0.4f)
-
- job.cancel()
- }
-
- @Test
fun listenForDozeAmountTransition_updatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index fb0fd23fab24..6bfd08025833 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -34,8 +34,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.model.SysUiState
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -82,7 +84,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
private val uiProperties =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = ENABLED,
- isAutoOnToggleFeatureAvailable = ENABLED
+ isAutoOnToggleFeatureAvailable = ENABLED,
)
@Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
@Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -98,6 +100,8 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate
private lateinit var deviceItem: DeviceItem
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
scheduler = TestCoroutineScheduler()
@@ -116,10 +120,16 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
fakeSystemClock,
uiEventLogger,
logger,
- sysuiDialogFactory
+ sysuiDialogFactory,
+ kosmos.shadeDialogContextInteractor,
)
- whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+ whenever(
+ sysuiDialogFactory.create(
+ any(SystemUIDialog.Delegate::class.java),
+ any()
+ )
+ ).thenAnswer {
SystemUIDialog(
mContext,
0,
@@ -128,7 +138,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
sysuiState,
fakeBroadcastDispatcher,
dialogTransitionAnimator,
- it.getArgument(0)
+ it.getArgument(0),
)
}
@@ -140,7 +150,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = icon,
- background = null
+ background = null,
)
`when`(cachedBluetoothDevice.isBusy).thenReturn(false)
}
@@ -169,7 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog,
listOf(deviceItem),
showSeeAll = false,
- showPairNewDevice = false
+ showPairNewDevice = false,
)
val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
@@ -217,6 +227,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
uiEventLogger,
logger,
sysuiDialogFactory,
+ kosmos.shadeDialogContextInteractor,
)
.Adapter(bluetoothTileDialogCallback)
.DeviceItemViewHolder(view)
@@ -238,7 +249,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog,
listOf(deviceItem),
showSeeAll = false,
- showPairNewDevice = true
+ showPairNewDevice = true,
)
val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button)
@@ -272,6 +283,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
uiEventLogger,
logger,
sysuiDialogFactory,
+ kosmos.shadeDialogContextInteractor,
)
.createDialog()
dialog.show()
@@ -295,6 +307,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
uiEventLogger,
logger,
sysuiDialogFactory,
+ kosmos.shadeDialogContextInteractor,
)
.createDialog()
dialog.show()
@@ -318,6 +331,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
uiEventLogger,
logger,
sysuiDialogFactory,
+ kosmos.shadeDialogContextInteractor,
)
.createDialog()
dialog.show()
@@ -339,7 +353,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog,
visibility = VISIBLE,
label = null,
- isActive = true
+ isActive = true,
)
val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
@@ -361,7 +375,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog,
visibility = VISIBLE,
label = null,
- isActive = false
+ isActive = false,
)
val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
@@ -383,7 +397,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog,
visibility = GONE,
label = null,
- isActive = false
+ isActive = false,
)
val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index c2c94a88603a..1cabf202463e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -77,6 +77,8 @@ public class CommunalTouchHandlerTest extends SysuiTestCase {
INITIATION_WIDTH,
mKosmos.getCommunalInteractor(),
mKosmos.getConfigurationInteractor(),
+ mKosmos.getSceneInteractor(),
+ Optional.of(mKosmos.getMockWindowRootViewProvider()),
mLifecycle
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 32fa160fc29f..21dd5bc068f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -344,7 +344,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
underTest.onQuickAffordanceTriggered(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 1184a76d54ff..1ce128c2403a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -81,7 +81,7 @@ import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@FlakyTest(
bugId = 292574995,
- detail = "on certain architectures all permutations with startActivity=true is causing failures"
+ detail = "on certain architectures all permutations with startActivity=true is causing failures",
)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@@ -93,11 +93,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
private val DRAWABLE =
mock<Icon> {
whenever(this.contentDescription)
- .thenReturn(
- ContentDescription.Resource(
- res = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- )
+ .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
@@ -273,13 +269,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
context = context,
userFileManager =
mock<UserFileManager>().apply {
- whenever(
- getSharedPreferences(
- anyString(),
- anyInt(),
- anyInt(),
- )
- )
+ whenever(getSharedPreferences(anyString(), anyInt(), anyInt()))
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
@@ -316,9 +306,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- )
+ KeyguardInteractorFactory.create(featureFlags = featureFlags)
.keyguardInteractor,
shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
@@ -350,9 +338,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
homeControls.setState(
lockScreenState =
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = DRAWABLE,
- )
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE)
)
homeControls.onTriggeredResult =
if (startActivity) {
@@ -361,7 +347,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(false)
}
underTest.onQuickAffordanceTriggered(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index b26f0a6e71a3..782b24825bcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -557,6 +557,13 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
}
@Test
+ public void startActivityForDialog_always_startActivityWithoutDismissShade() {
+ mInternetDialogController.startActivityForDialog(mock(Intent.class));
+
+ verify(mActivityStarter).startActivity(any(Intent.class), eq(false) /* dismissShade */);
+ }
+
+ @Test
public void launchWifiDetailsSetting_withNoWifiEntryKey_doNothing() {
mInternetDialogController.launchWifiDetailsSetting(null /* key */, mDialogLaunchView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 300c9b843d1c..8560b67dee33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -35,6 +35,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -149,7 +150,8 @@ public class InternetDialogDelegateTest extends SysuiTestCase {
mHandler,
mBgExecutor,
mKeyguard,
- mSystemUIDialogFactory);
+ mSystemUIDialogFactory,
+ new FakeShadeDialogContextInteractor(mContext));
mInternetDialogDelegate.createDialog();
mInternetDialogDelegate.onCreate(mSystemUIDialog, null);
mInternetDialogDelegate.mAdapter = mInternetAdapter;
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 a0ecb802dd61..f695c13a9e62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -76,6 +76,8 @@ class UserTrackerImplTest : SysuiTestCase() {
@Mock private lateinit var iActivityManager: IActivityManager
+ @Mock private lateinit var beforeUserSwitchingReply: IRemoteCallback
+
@Mock private lateinit var userSwitchingReply: IRemoteCallback
@Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
@@ -199,9 +201,10 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
+ captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
+ verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
verify(userManager).getProfiles(newID)
@@ -341,10 +344,11 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
+ captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
+ verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
assertThat(callback.calledOnUserChanging).isEqualTo(1)
assertThat(callback.lastUser).isEqualTo(newID)
@@ -395,7 +399,7 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
- captor.value.onBeforeUserSwitching(newID)
+ captor.value.onBeforeUserSwitching(newID, any())
captor.value.onUserSwitchComplete(newID)
runCurrent()
@@ -453,8 +457,10 @@ class UserTrackerImplTest : SysuiTestCase() {
val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+ captor.value.onBeforeUserSwitching(newID, beforeUserSwitchingReply)
captor.value.onUserSwitching(newID, userSwitchingReply)
runCurrent()
+ verify(beforeUserSwitchingReply).sendResult(any())
verify(userSwitchingReply).sendResult(any())
captor.value.onUserSwitchComplete(newID)
@@ -488,6 +494,7 @@ class UserTrackerImplTest : SysuiTestCase() {
}
private class TestCallback : UserTracker.Callback {
+ var calledOnBeforeUserChanging = 0
var calledOnUserChanging = 0
var calledOnUserChanged = 0
var calledOnProfilesChanged = 0
@@ -495,6 +502,11 @@ class UserTrackerImplTest : SysuiTestCase() {
var lastUserContext: Context? = null
var lastUserProfiles = emptyList<UserInfo>()
+ override fun onBeforeUserSwitching(newUser: Int) {
+ calledOnBeforeUserChanging++
+ lastUser = newUser
+ }
+
override fun onUserChanging(newUser: Int, userContext: Context) {
calledOnUserChanging++
lastUser = newUser
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 041d1a611b55..4b11e2c24722 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.content.Context
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.FlagsParameterization
@@ -32,7 +31,6 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityContainerController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -406,18 +404,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- @DisableSceneContainer
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() {
- underTest.setStatusBarViewController(phoneStatusBarViewController)
-
- interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
-
- verify(centralSurfaces, times(0)).userActivity()
- }
-
- @Test
- @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() {
underTest.setStatusBarViewController(phoneStatusBarViewController)
@@ -438,7 +424,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() {
// GIVEN dozing
whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -451,7 +436,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() {
// GIVEN dozing
whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -464,7 +448,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() {
// GIVEN dozing
whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
@@ -609,7 +592,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
}
@Test
- @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun cancelCurrentTouch_callsDragDownHelper() {
underTest.cancelCurrentTouch()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 13bc82fa2c70..a04ca038021e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade
-import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -27,7 +26,6 @@ import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
@@ -65,10 +63,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-/**
- * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests
- * will be deleted.
- */
+/** NotificationsQSContainerController tests */
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -122,7 +117,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
delayableExecutor,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
- largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
+ largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
)
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
@@ -209,23 +204,23 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
given(
taskbarVisible = true,
navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = STABLE_INSET_BOTTOM,
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
}
@@ -237,22 +232,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = 0,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
given(
taskbarVisible = false,
navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
}
@@ -263,22 +258,22 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout()
+ insets = windowInsets().withCutout(),
)
then(
expectedContainerPadding = CUTOUT_HEIGHT,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
given(
taskbarVisible = false,
navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom()
+ insets = windowInsets().withCutout().withStableBottom(),
)
then(
expectedContainerPadding = 0,
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET,
)
}
@@ -289,18 +284,18 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
given(
taskbarVisible = true,
navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedQsPadding = STABLE_INSET_BOTTOM
+ expectedQsPadding = STABLE_INSET_BOTTOM,
)
}
@@ -314,19 +309,19 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom()
+ insets = windowInsets().withCutout().withStableBottom(),
)
then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
given(
taskbarVisible = false,
navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(
expectedContainerPadding = 0,
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM
+ expectedQsPadding = STABLE_INSET_BOTTOM,
)
}
@@ -339,7 +334,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
@@ -355,7 +350,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
given(
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom()
+ insets = windowInsets().withStableBottom(),
)
then(expectedContainerPadding = 0)
@@ -376,43 +371,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testSplitShadeLayout_isAlignedToGuideline() {
- enableSplitShade()
- underTest.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(R.id.qs_edge_guideline)
- }
-
- @Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testSinglePaneLayout_childrenHaveEqualMargins() {
- disableSplitShade()
- underTest.updateResources()
- val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
- val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
- val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
- val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
- assertThat(
- qsStartMargin == qsEndMargin &&
- notifStartMargin == notifEndMargin &&
- qsStartMargin == notifStartMargin
- )
- .isTrue()
- }
-
- @Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- enableSplitShade()
- underTest.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(0)
- }
-
- @Test
fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
enableSplitShade()
underTest.updateResources()
@@ -421,37 +379,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
- setLargeScreen()
- val largeScreenHeaderResourceHeight = 100
- val largeScreenHeaderHelperHeight = 200
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(largeScreenHeaderHelperHeight)
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
- // ensure the estimated height (would be 30 here) wouldn't impact this test case
- overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
- overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
- underTest.updateResources()
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHelperHeight)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderHelperHeight)
- }
-
- @Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- setSmallScreen()
- underTest.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
- }
-
- @Test
fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
disableSplitShade()
underTest.updateResources()
@@ -464,17 +391,6 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun testSinglePaneShadeLayout_isAlignedToParent() {
- disableSplitShade()
- underTest.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(ConstraintSet.PARENT_ID)
- }
-
- @Test
fun testAllChildrenOfNotificationContainer_haveIds() {
// set dimen to 0 to avoid triggering updating bottom spacing
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
@@ -493,7 +409,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
delayableExecutor,
notificationStackScrollLayoutController,
ResourcesSplitShadeStateController(),
- largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
+ largeScreenHeaderHelperLazy = { largeScreenHeaderHelper },
)
controller.updateConstraints()
@@ -509,7 +425,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
insets = emptyInsets(),
- applyImmediately = false
+ applyImmediately = false,
)
fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
windowInsetsCallback.accept(windowInsets().withStableBottom())
@@ -576,7 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
taskbarVisible: Boolean,
navigationMode: Int,
insets: WindowInsets,
- applyImmediately: Boolean = true
+ applyImmediately: Boolean = true,
) {
Mockito.clearInvocations(view)
taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
@@ -591,7 +507,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
fun then(
expectedContainerPadding: Int,
expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
- expectedQsPadding: Int = 0
+ expectedQsPadding: Int = 0,
) {
verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
@@ -623,7 +539,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
val layoutParams =
ConstraintLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
// required as cloning ConstraintSet fails if view doesn't have layout params
view.layoutParams = layoutParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index 503fa789cb80..1eb88c5a5616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -89,6 +89,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
messagingStyle = null,
builder = notificationBuilder,
systemUiContext = context,
+ redactText = false,
)
// WHEN: binds the viewHolder
@@ -149,6 +150,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
messagingStyle = style,
builder = notificationBuilder,
systemUiContext = context,
+ redactText = false,
)
// WHEN: binds the view
SingleLineViewBinder.bind(viewModel, view)
@@ -197,6 +199,7 @@ class SingleLineViewBinderTest : SysuiTestCase() {
messagingStyle = null,
builder = notificationBuilder,
systemUiContext = context,
+ redactText = false,
)
// WHEN: binds the view with the view model
SingleLineViewBinder.bind(viewModel, view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
index d3666321c8e4..ef70e277832e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -379,7 +379,8 @@ class SingleLineViewInflaterTest : SysuiTestCase() {
this,
if (isConversation) messagingStyle else null,
builder,
- context
+ context,
+ false
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a91fb45c9c80..e1a891662889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -1544,14 +1544,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
assertFalse(mStackScroller.mHeadsUpAnimatingAway);
}
- @Test
- @EnableSceneContainer
- public void finishExpanding_sceneContainerEnabled() {
- mStackScroller.startOverscrollAfterExpanding();
- verify(mStackScroller.getExpandHelper()).finishExpanding();
- assertTrue(mStackScroller.getIsBeingDragged());
- }
-
private MotionEvent captureTouchSentToSceneFramework() {
ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index 76fc61185b49..25d1c377ecbd 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -433,6 +433,8 @@ class FakeStatusBarService : IStatusBarService.Stub() {
override fun showRearDisplayDialog(currentBaseState: Int) {}
+ override fun unbundleNotification(key: String) {}
+
companion object {
const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
const val SECONDARY_DISPLAY_ID = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt
new file mode 100644
index 000000000000..57c8fd066ea8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalBackActionInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.communalBackActionInteractor by
+ Kosmos.Fixture {
+ CommunalBackActionInteractor(
+ communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index ad92b318b0b9..bfc424848900 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -86,12 +86,8 @@ suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
}
suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
- setCommunalV2ConfigEnabled(true)
- if (enabled) {
- fakeUserRepository.asMainUser()
- } else {
- fakeUserRepository.asDefaultUser()
- }
+ setCommunalV2ConfigEnabled(enabled)
+ setCommunalEnabled(enabled)
}
suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
index b407b1ba227a..e3cfb80489e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt
@@ -17,8 +17,10 @@
package com.android.systemui.communal.ui.viewmodel
import android.service.dream.dreamManager
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.policy.batteryController
val Kosmos.communalToDreamButtonViewModel by
@@ -26,6 +28,8 @@ val Kosmos.communalToDreamButtonViewModel by
CommunalToDreamButtonViewModel(
backgroundContext = testDispatcher,
batteryController = batteryController,
+ settingsInteractor = communalSettingsInteractor,
+ activityStarter = activityStarter,
dreamManager = dreamManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 534ded57eb85..9012393badd6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -58,4 +58,26 @@ class FakeDisplayWindowPropertiesRepository(private val context: Context) :
fun insert(instance: DisplayWindowProperties) {
properties.put(instance.displayId, instance.windowType, instance)
}
+
+ /** inserts an entry, mocking everything except the context. */
+ fun insertForContext(displayId: Int, windowType: Int, context: Context) {
+ properties.put(
+ displayId,
+ windowType,
+ DisplayWindowProperties(
+ displayId = displayId,
+ windowType = windowType,
+ context = context,
+ windowManager = mock(),
+ layoutInflater = mock(),
+ ),
+ )
+ }
+
+ /** Whether the repository contains an entry already. */
+ fun contains(displayId: Int, windowType: Int): Boolean =
+ properties.contains(displayId, windowType)
+
+ /** Removes all the entries. */
+ fun clear() = properties.clear()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index 548b5646f5d4..5d206691b520 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -30,7 +30,7 @@ class FakeKeyguardQuickAffordanceConfig(
override val pickerIconResourceId: Int = 0,
) : KeyguardQuickAffordanceConfig {
- var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
+ var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled(false)
private val _lockScreenState =
MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>(
@@ -41,9 +41,7 @@ class FakeKeyguardQuickAffordanceConfig(
override fun pickerName(): String = pickerName
- override fun onTriggered(
- expandable: Expandable?,
- ): OnTriggeredResult {
+ override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
return onTriggeredResult
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt
new file mode 100644
index 000000000000..d857157137b6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceHapticViewModelKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.flow.Flow
+
+val Kosmos.keyguardQuickAffordanceHapticViewModelFactory by
+ Kosmos.Fixture {
+ object : KeyguardQuickAffordanceHapticViewModel.Factory {
+ override fun create(
+ quickAffordanceViewModel: Flow<KeyguardQuickAffordanceViewModel>
+ ): KeyguardQuickAffordanceHapticViewModel =
+ KeyguardQuickAffordanceHapticViewModel(
+ quickAffordanceViewModel,
+ keyguardQuickAffordanceInteractor,
+ )
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index e47310727905..abbfa93edd17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -87,7 +87,6 @@ val Kosmos.keyguardRootViewModel by Fixture {
primaryBouncerToLockscreenTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
- aodAlphaViewModel = aodAlphaViewModel,
shadeInteractor = shadeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index d941fb049835..43835607c77d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -17,6 +17,7 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.mockito.kotlin.verify
var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
@@ -82,6 +83,32 @@ fun <T> TestScope.currentValue(stateFlow: StateFlow<T>): T {
}
/** Retrieve the current value of this [StateFlow] safely. See `currentValue(TestScope)`. */
+fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn)
+
+/**
+ * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of
+ * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue]
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.currentValue(fn: () -> T): T {
+ runCurrent()
+ return fn()
+}
+
+/** Retrieve the result of [fn] after running all pending tasks. See `TestScope.currentValue(fn)` */
fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T {
return testScope.currentValue(stateFlow)
}
+
+/** Safely verify that a mock has been called after the test scope has caught up */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T> TestScope.verifyCurrent(mock: T): T {
+ runCurrent()
+ return verify(mock)
+}
+
+/**
+ * Safely verify that a mock has been called after the test scope has caught up. See
+ * `TestScope.verifyCurrent`
+ */
+fun <T> Kosmos.verifyCurrent(mock: T) = testScope.verifyCurrent(mock)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 41cfceaa5e38..39f1ad42797b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -63,6 +63,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.scrimStartable
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.scene.ui.view.mockWindowRootViewProvider
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -191,4 +192,5 @@ class KosmosJavaAdapter() {
}
val disableFlagsInteractor by lazy { kosmos.disableFlagsInteractor }
val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
+ val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
index 0ec8d49ec29b..49bbbef6ccc0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/ActivityStarterKosmos.kt
@@ -17,6 +17,6 @@
package com.android.systemui.plugins
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
var Kosmos.activityStarter by Kosmos.Fixture { mock<ActivityStarter>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index ab1c1818bf80..aa29808bd9ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -45,7 +45,6 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
other ?: return
}
check("icon").that(actual.icon).isEqualTo(other.icon)
- check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
check("label").that(actual.label).isEqualTo(other.label)
check("activationState").that(actual.activationState).isEqualTo(other.activationState)
check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
index f52572a9e42d..f5eebb46c2ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -19,13 +19,13 @@ package com.android.systemui.scene.shared.model
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.kosmos.currentValue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.TestScope
-class FakeSceneDataSource(
- initialSceneKey: SceneKey,
-) : SceneDataSource {
+class FakeSceneDataSource(initialSceneKey: SceneKey, val testScope: TestScope) : SceneDataSource {
private val _currentScene = MutableStateFlow(initialSceneKey)
override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
@@ -33,18 +33,20 @@ class FakeSceneDataSource(
private val _currentOverlays = MutableStateFlow<Set<OverlayKey>>(emptySet())
override val currentOverlays: StateFlow<Set<OverlayKey>> = _currentOverlays.asStateFlow()
- var isPaused = false
- private set
+ private var _isPaused = false
+ val isPaused
+ get() = testScope.currentValue { _isPaused }
- var pendingScene: SceneKey? = null
- private set
+ private var _pendingScene: SceneKey? = null
+ val pendingScene
+ get() = testScope.currentValue { _pendingScene }
var pendingOverlays: Set<OverlayKey>? = null
private set
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
- if (isPaused) {
- pendingScene = toScene
+ if (_isPaused) {
+ _pendingScene = toScene
} else {
_currentScene.value = toScene
}
@@ -55,7 +57,7 @@ class FakeSceneDataSource(
}
override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
- if (isPaused) {
+ if (_isPaused) {
pendingOverlays = (pendingOverlays ?: currentOverlays.value) + overlay
} else {
_currentOverlays.value += overlay
@@ -63,7 +65,7 @@ class FakeSceneDataSource(
}
override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) {
- if (isPaused) {
+ if (_isPaused) {
pendingOverlays = (pendingOverlays ?: currentOverlays.value) - overlay
} else {
_currentOverlays.value -= overlay
@@ -82,9 +84,9 @@ class FakeSceneDataSource(
* last one will be remembered.
*/
fun pause() {
- check(!isPaused) { "Can't pause what's already paused!" }
+ check(!_isPaused) { "Can't pause what's already paused!" }
- isPaused = true
+ _isPaused = true
}
/**
@@ -100,15 +102,12 @@ class FakeSceneDataSource(
*
* If [expectedScene] is provided, will assert that it's indeed the latest called.
*/
- fun unpause(
- force: Boolean = false,
- expectedScene: SceneKey? = null,
- ) {
- check(force || isPaused) { "Can't unpause what's already not paused!" }
-
- isPaused = false
- pendingScene?.let { _currentScene.value = it }
- pendingScene = null
+ fun unpause(force: Boolean = false, expectedScene: SceneKey? = null) {
+ check(force || _isPaused) { "Can't unpause what's already not paused!" }
+
+ _isPaused = false
+ _pendingScene?.let { _currentScene.value = it }
+ _pendingScene = null
pendingOverlays?.let { _currentOverlays.value = it }
pendingOverlays = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
index f5196866ae6f..7eebfc305682 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
@@ -19,13 +19,12 @@ package com.android.systemui.scene.shared.model
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.initialSceneKey
import com.android.systemui.scene.sceneContainerConfig
val Kosmos.fakeSceneDataSource by Fixture {
- FakeSceneDataSource(
- initialSceneKey = initialSceneKey,
- )
+ FakeSceneDataSource(initialSceneKey = initialSceneKey, testScope = testScope)
}
val Kosmos.sceneDataSourceDelegator by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
index 5c91dc80b3d4..e6ba9a581836 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKosmos.kt
@@ -17,6 +17,9 @@
package com.android.systemui.scene.ui.view
import com.android.systemui.kosmos.Kosmos
+import javax.inject.Provider
import org.mockito.kotlin.mock
val Kosmos.mockShadeRootView by Kosmos.Fixture { mock<WindowRootView>() }
+val Kosmos.mockWindowRootViewProvider by
+ Kosmos.Fixture { Provider<WindowRootView> { mock<WindowRootView>() } }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt
new file mode 100644
index 000000000000..18c44bac3ff1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/NotificationShadeWindowViewKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.notificationShadeWindowView by Kosmos.Fixture { mock<NotificationShadeWindowView>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
index 4dcd2208b152..3ed730271bc3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt
@@ -16,6 +16,14 @@
package com.android.systemui.shade.data.repository
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() }
+val Kosmos.shadeDialogContextInteractor by
+ Kosmos.Fixture {
+ mock<ShadeDialogContextInteractor> { on { context } doReturn applicationContext }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
index e5a75d59468d..9f4091ccfc26 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionControllerKosmos.kt
@@ -18,8 +18,14 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.lockscreenShadeKeyguardTransitionController by Fixture {
+ mock<LockscreenShadeKeyguardTransitionController>()
+}
var Kosmos.lockscreenShadeKeyguardTransitionControllerFactory by Fixture {
- mock<LockscreenShadeKeyguardTransitionController.Factory>()
+ LockscreenShadeKeyguardTransitionController.Factory {
+ lockscreenShadeKeyguardTransitionController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
index 27679804d11f..fc52e454a1c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerKosmos.kt
@@ -18,8 +18,12 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.lockscreenShadeQsTransitionController by Fixture {
+ mock<LockscreenShadeQsTransitionController>()
+}
var Kosmos.lockscreenShadeQsTransitionControllerFactory by Fixture {
- mock<LockscreenShadeQsTransitionController.Factory>()
+ LockscreenShadeQsTransitionController.Factory { lockscreenShadeQsTransitionController }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index e4a3896378f6..1a451ce01525 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -36,7 +36,7 @@ import com.android.systemui.statusbar.phone.lsShadeTransitionLogger
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.splitShadeStateController
-val Kosmos.lockscreenShadeTransitionController by Fixture {
+var Kosmos.lockscreenShadeTransitionController by Fixture {
LockscreenShadeTransitionController(
statusBarStateController = sysuiStatusBarStateController,
logger = lsShadeTransitionLogger,
@@ -62,6 +62,6 @@ val Kosmos.lockscreenShadeTransitionController by Fixture {
splitShadeStateController = splitShadeStateController,
shadeLockscreenInteractorLazy = { shadeLockscreenInteractor },
naturalScrollingSettingObserver = naturalScrollingSettingObserver,
- lazyQSSceneAdapter = { qsSceneAdapter }
+ lazyQSSceneAdapter = { qsSceneAdapter },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
index 43e39c05f6e9..8a68eef2f371 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerKosmos.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
var Kosmos.singleShadeLockScreenOverScrollerFactory by Fixture {
mock<SingleShadeLockScreenOverScroller.Factory>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
index 017371a6cba8..e491dffb0ed5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerKosmos.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
+
+var Kosmos.splitShadeLockScreenOverScroller by Fixture { mock<SplitShadeLockScreenOverScroller>() }
var Kosmos.splitShadeLockScreenOverScrollerFactory by Fixture {
- mock<SplitShadeLockScreenOverScroller.Factory>()
+ SplitShadeLockScreenOverScroller.Factory { _, _ -> splitShadeLockScreenOverScroller }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
new file mode 100644
index 000000000000..62cdc87f980f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
+ Kosmos.Fixture { StatusBarPopupChipsViewModel(testScope.backgroundScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt
new file mode 100644
index 000000000000..360e9e93f18d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisualInterruptionDecisionProviderKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
+import org.mockito.kotlin.mock
+
+val Kosmos.visualInterruptionDecisionProvider by
+ Kosmos.Fixture { mock<VisualInterruptionDecisionProvider>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt
new file mode 100644
index 000000000000..768952d1ee77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.notificationAlertsInteractor by Kosmos.Fixture { mock<NotificationAlertsInteractor>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
new file mode 100644
index 000000000000..680e0de22794
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import org.junit.Assert
+
+class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor {
+ @JvmField
+ val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>()
+ @JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>()
+
+ override fun extractContent(
+ entry: NotificationEntry,
+ recoveredBuilder: Notification.Builder,
+ ): PromotedNotificationContentModel? {
+ extractCalls.add(entry to recoveredBuilder)
+
+ if (contentForEntry.isEmpty()) {
+ // If *no* entries are set, just return null for everything.
+ return null
+ } else {
+ // If entries *are* set, fail on unexpected ones.
+ Assert.assertTrue(contentForEntry.containsKey(entry))
+ return contentForEntry.get(entry)
+ }
+ }
+
+ fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) {
+ contentForEntry.clear()
+ contentForEntry.put(entry, content)
+ extractCalls.clear()
+ }
+
+ fun verifyZeroExtractCalls() {
+ Assert.assertTrue(extractCalls.isEmpty())
+ }
+
+ fun verifyOneExtractCall() {
+ Assert.assertEquals(1, extractCalls.size)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
index 88caf6e2ba30..ea7b41d43871 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
@@ -17,11 +17,20 @@
package com.android.systemui.statusbar.notification.promoted
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert
class FakePromotedNotificationsProvider : PromotedNotificationsProvider {
val promotedEntries = mutableSetOf<NotificationEntry>()
+ val shouldPromoteForEntry = mutableMapOf<NotificationEntry, Boolean>()
override fun shouldPromote(entry: NotificationEntry): Boolean {
- return promotedEntries.contains(entry)
+ if (shouldPromoteForEntry.isEmpty()) {
+ // If *no* entries are set, just return false for everything.
+ return false
+ } else {
+ // If entries *are* set, fail on unexpected ones.
+ Assert.assertTrue(shouldPromoteForEntry.containsKey(entry))
+ return shouldPromoteForEntry[entry] ?: false
+ }
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index 5e9f12b4b1cc..52c17c82fb12 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -21,7 +21,7 @@ import com.android.systemui.kosmos.Kosmos
var Kosmos.promotedNotificationContentExtractor by
Kosmos.Fixture {
- PromotedNotificationContentExtractor(
+ PromotedNotificationContentExtractorImpl(
promotedNotificationsProvider,
applicationContext,
promotedNotificationLogger,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 7126933154df..e739e82aa8a8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -65,7 +65,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.icon.IconBuilder
import com.android.systemui.statusbar.notification.icon.IconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
@@ -222,16 +222,11 @@ class ExpandableNotificationRowBuilder(
Mockito.mock(LauncherApps::class.java, STUB_ONLY),
Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY),
)
-
- val promotedNotificationsProvider = PromotedNotificationsProviderImpl()
- val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog")
- val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog)
-
val promotedNotificationContentExtractor =
- PromotedNotificationContentExtractor(
- promotedNotificationsProvider,
+ PromotedNotificationContentExtractorImpl(
+ PromotedNotificationsProviderImpl(),
context,
- promotedNotificationLogger,
+ PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")),
)
mContentBinder =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
index f19ac1e5a58d..26642d4f7534 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.kosmos.Kosmos
-import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
var Kosmos.keyguardStateController: KeyguardStateController by
- Kosmos.Fixture { mock(KeyguardStateController::class.java) }
+ Kosmos.Fixture { mock<KeyguardStateController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 932e768676cb..6c98d19db5d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.animation.dialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.statusbar.phone.systemUIDialogFactory
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel
import com.android.systemui.util.mockito.mock
@@ -35,5 +36,6 @@ var Kosmos.modesDialogDelegate: ModesDialogDelegate by
{ modesDialogViewModel },
modesDialogEventLogger,
mainCoroutineContext,
+ shadeDialogContextInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
index a3572754ab19..f31697e82a45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
@@ -70,6 +70,14 @@ class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy {
}
}
+ fun getContentObservers(uri: Uri, userHandle: Int): List<ContentObserver> {
+ if (userHandle == UserHandle.USER_ALL) {
+ return contentObserversAllUsers[uri.toString()] ?: listOf()
+ } else {
+ return contentObservers[SettingsKey(userHandle, uri.toString())] ?: listOf()
+ }
+ }
+
override fun getContentResolver(): ContentResolver {
throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented")
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3441d94facda..9ceca5d1dbfe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1589,7 +1589,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
* lock because this calls out to WindowManagerService.
*/
void addWindowTokensForAllDisplays() {
- final Display[] displays = mDisplayManager.getDisplays();
+ Display[] displays = {};
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ displays = mDisplayManager.getDisplays();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
for (int i = 0; i < displays.length; i++) {
final int displayId = displays[i].getDisplayId();
addWindowTokenForDisplay(displayId);
@@ -1625,7 +1631,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
public void onRemoved() {
- final Display[] displays = mDisplayManager.getDisplays();
+ Display[] displays = {};
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ displays = mDisplayManager.getDisplays();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
for (int i = 0; i < displays.length; i++) {
final int displayId = displays[i].getDisplayId();
onDisplayRemoved(displayId);
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3025e2eaede0..549f8fa77b53 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -217,6 +217,13 @@ public class UserBackupManagerService {
+ mPowerManagerWakeLock.getTag()));
return;
}
+
+ if (!mPowerManagerWakeLock.isHeld()) {
+ Slog.w(TAG, addUserIdToLogMessage(mUserId,
+ "Wakelock not held: " + mPowerManagerWakeLock.getTag()));
+ return;
+ }
+
mPowerManagerWakeLock.release();
Slog.v(
TAG,
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index fd18fa856916..abfb8268bd9a 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -55,6 +55,7 @@ import android.content.Intent;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
+import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -102,6 +103,7 @@ public class ContextualSearchManagerService extends SystemService {
private final PackageManagerInternal mPackageManager;
private final WindowManagerInternal mWmInternal;
private final DevicePolicyManagerInternal mDpmInternal;
+ private final AudioManager mAudioManager;
private final Object mLock = new Object();
private final AssistDataRequester mAssistDataRequester;
@@ -163,6 +165,8 @@ public class ContextualSearchManagerService extends SystemService {
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+ mAudioManager = context.getSystemService(AudioManager.class);
+
mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class));
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mAssistDataRequester = new AssistDataRequester(
@@ -306,6 +310,10 @@ public class ContextualSearchManagerService extends SystemService {
SystemClock.uptimeMillis());
launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint);
launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken);
+ if (Flags.includeAudioPlayingStatus()) {
+ launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_AUDIO_PLAYING,
+ mAudioManager.isMusicActive());
+ }
boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
final List<IBinder> activityTokens = new ArrayList<>(records.size());
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4944cafeb83d..4b8770b3cd35 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
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;
@@ -31,6 +32,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA
import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean;
import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
import android.app.ForegroundServiceTypePolicy;
import android.content.ComponentName;
@@ -55,6 +57,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
import dalvik.annotation.optimization.NeverCompile;
@@ -181,6 +184,12 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION =
"follow_up_oomadj_update_wait_duration";
+ /*
+ * Oom score cutoff beyond which any process that does not have the CPU_TIME capability will be
+ * frozen.
+ */
+ static final String KEY_FREEZER_CUTOFF_ADJ = "freezer_cutoff_adj";
+
private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -267,6 +276,9 @@ final class ActivityManagerConstants extends ContentObserver {
*/
private static final long DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = 1000L;
+ /** The default value to {@link #KEY_FREEZER_CUTOFF_ADJ} */
+ private static final int DEFAULT_FREEZER_CUTOFF_ADJ = ProcessList.CACHED_APP_MIN_ADJ;
+
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
*/
@@ -1171,6 +1183,14 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION;
/**
+ * The cutoff adj for the freezer, app processes with adj greater than this value will be
+ * eligible for the freezer.
+ *
+ * @see #KEY_FREEZER_CUTOFF_ADJ
+ */
+ public int FREEZER_CUTOFF_ADJ = DEFAULT_FREEZER_CUTOFF_ADJ;
+
+ /**
* Indicates whether PSS profiling in AppProfiler is disabled or not.
*/
static final String KEY_DISABLE_APP_PROFILER_PSS_PROFILING =
@@ -1194,6 +1214,7 @@ final class ActivityManagerConstants extends ContentObserver {
new OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(Properties properties) {
+ boolean oomAdjusterConfigUpdated = false;
for (String name : properties.getKeyset()) {
if (name == null) {
return;
@@ -1372,6 +1393,11 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_TIERED_CACHED_ADJ_UI_TIER_SIZE:
updateUseTieredCachedAdj();
break;
+ case KEY_FREEZER_CUTOFF_ADJ:
+ FREEZER_CUTOFF_ADJ = properties.getInt(KEY_FREEZER_CUTOFF_ADJ,
+ DEFAULT_FREEZER_CUTOFF_ADJ);
+ oomAdjusterConfigUpdated = true;
+ break;
case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
updateDisableAppProfilerPssProfiling();
break;
@@ -1389,6 +1415,13 @@ final class ActivityManagerConstants extends ContentObserver {
break;
}
}
+ if (oomAdjusterConfigUpdated) {
+ final ActivityManagerInternal ami = LocalServices.getService(
+ ActivityManagerInternal.class);
+ if (ami != null) {
+ ami.updateOomAdj(OOM_ADJ_REASON_RECONFIGURATION);
+ }
+ }
}
};
@@ -2534,6 +2567,9 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ);
pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
+ pw.print(" "); pw.print(KEY_FREEZER_CUTOFF_ADJ);
+ pw.print("="); pw.println(FREEZER_CUTOFF_ADJ);
+
pw.print(" "); pw.print(KEY_DISABLE_APP_PROFILER_PSS_PROFILING);
pw.print("="); pw.println(APP_PROFILER_PSS_PROFILING_DISABLED);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a184e905d0fa..50b6990c0c1c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19396,9 +19396,6 @@ public class ActivityManagerService extends IActivityManager.Stub
creatorPackage);
if (creatorToken != null) {
extraIntent.setCreatorToken(creatorToken);
- // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
- Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
- + creatorPackage + "; intent: " + extraIntent);
FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED, creatorUid, false);
}
});
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9a63546bf5a7..cbebc905796c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -449,6 +449,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runSetAppZygotePreloadTimeout(pw);
case "set-media-foreground-service":
return runSetMediaForegroundService(pw);
+ case "clear-bad-process":
+ return runClearBadProcess(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -4276,6 +4278,27 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ int runClearBadProcess(PrintWriter pw) throws RemoteException {
+ final String processName = getNextArgRequired();
+ int userId = UserHandle.USER_CURRENT;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if ("--user".equals(opt)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: unknown option " + opt);
+ return -1;
+ }
+ }
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mInternal.getCurrentUserId();
+ }
+
+ pw.println("Clearing '" + processName + "' in u" + userId + " from bad processes list");
+ mInternal.mAppErrors.clearBadProcessForUser(processName, userId);
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
@@ -4717,6 +4740,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" set-media-foreground-service inactive|active [--user USER_ID] <PACKAGE>"
+ " <NOTIFICATION_ID>");
pw.println(" Set an app's media service inactive or active.");
+ pw.println(" clear-bad-process [--user USER_ID] <PROCESS_NAME>");
+ pw.println(" Clears a process from the bad processes list.");
Intent.printIntentArgsHelp(pw, "");
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index b7a5f3e4099a..2fbf05eb0061 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -373,6 +373,24 @@ class AppErrors {
}
}
+ void clearBadProcessForUser(final String processName, final int userId) {
+ synchronized (mBadProcessLock) {
+ final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
+ badProcesses.putAll(mBadProcesses);
+ final SparseArray<BadProcessInfo> uids = badProcesses.get(processName);
+ if (uids == null) {
+ return;
+ }
+ for (int i = uids.size() - 1; i >= 0; --i) {
+ final int uid = uids.keyAt(i);
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.getUserId(uid)) {
+ badProcesses.remove(processName, uid);
+ }
+ }
+ mBadProcesses = badProcesses;
+ }
+ }
+
void markBadProcess(final String processName, final int uid, BadProcessInfo info) {
synchronized (mBadProcessLock) {
final ProcessMap<BadProcessInfo> badProcesses = new ProcessMap<>();
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 2f5362f53361..d335529a006a 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -25,9 +25,11 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FOLLOW_UP;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
@@ -203,6 +205,10 @@ public class CachedAppOptimizer {
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
static final int UNFREEZE_REASON_COMPONENT_DISABLED =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
+ static final int UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_FOLLOW_UP;
+ static final int UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_OOM_ADJ_RECONFIGURATION;
@IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
UNFREEZE_REASON_NONE,
@@ -234,6 +240,8 @@ public class CachedAppOptimizer {
UNFREEZE_REASON_EXECUTING_SERVICE,
UNFREEZE_REASON_RESTRICTION_CHANGE,
UNFREEZE_REASON_COMPONENT_DISABLED,
+ UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP,
+ UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnfreezeReason {}
@@ -2451,8 +2459,8 @@ public class CachedAppOptimizer {
synchronized (mAm.mPidsSelfLocked) {
pr = mAm.mPidsSelfLocked.get(blocked);
}
- if (pr != null
- && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) {
+ if (pr != null && pr.mState.getCurAdj()
+ < mAm.mConstants.FREEZER_CUTOFF_ADJ) {
Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ pr.processName + " (" + blocked + ")");
// Found at least one blocked non-cached process
@@ -2539,6 +2547,10 @@ public class CachedAppOptimizer {
return UNFREEZE_REASON_RESTRICTION_CHANGE;
case OOM_ADJ_REASON_COMPONENT_DISABLED:
return UNFREEZE_REASON_COMPONENT_DISABLED;
+ case OOM_ADJ_REASON_FOLLOW_UP:
+ return UNFREEZE_REASON_OOM_ADJ_FOLLOW_UP;
+ case OOM_ADJ_REASON_RECONFIGURATION:
+ return UNFREEZE_REASON_OOM_ADJ_RECONFIGURATION;
default:
return UNFREEZE_REASON_NONE;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index aadf6f61956c..9c569db99797 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -55,6 +55,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RECONFIGURATION;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
@@ -105,7 +106,6 @@ import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
-import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ;
import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.INVALID_ADJ;
@@ -232,6 +232,8 @@ public class OomAdjuster {
return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
case OOM_ADJ_REASON_FOLLOW_UP:
return AppProtoEnums.OOM_ADJ_REASON_FOLLOW_UP;
+ case OOM_ADJ_REASON_RECONFIGURATION:
+ return AppProtoEnums.OOM_ADJ_REASON_RECONFIGURATION;
default:
return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
}
@@ -288,6 +290,8 @@ public class OomAdjuster {
return OOM_ADJ_REASON_METHOD + "_componentDisabled";
case OOM_ADJ_REASON_FOLLOW_UP:
return OOM_ADJ_REASON_METHOD + "_followUp";
+ case OOM_ADJ_REASON_RECONFIGURATION:
+ return OOM_ADJ_REASON_METHOD + "_reconfiguration";
default:
return "_unknown";
}
@@ -4079,7 +4083,7 @@ public class OomAdjuster {
}
// Reasons to freeze:
- if (proc.mState.getCurAdj() >= FREEZER_CUTOFF_ADJ) {
+ if (proc.mState.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ) {
// Oomscore is in a high enough state, it is safe to freeze.
return true;
}
@@ -4098,9 +4102,8 @@ public class OomAdjuster {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
final ProcessStateRecord state = app.mState;
if (Flags.traceUpdateAppFreezeStateLsp()) {
- final boolean oomAdjChanged =
- (state.getCurAdj() >= FREEZER_CUTOFF_ADJ ^ oldOomAdj >= FREEZER_CUTOFF_ADJ)
- || oldOomAdj == UNKNOWN_ADJ;
+ final boolean oomAdjChanged = (state.getCurAdj() >= mConstants.FREEZER_CUTOFF_ADJ
+ ^ oldOomAdj >= mConstants.FREEZER_CUTOFF_ADJ) || oldOomAdj == UNKNOWN_ADJ;
final boolean shouldNotFreezeChanged = opt.shouldNotFreezeAdjSeq() == mAdjSeq;
final boolean hasCpuCapability =
(PROCESS_CAPABILITY_CPU_TIME & app.mState.getCurCapability())
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 70febcd63455..bddde9d589f3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -383,12 +383,6 @@ public final class ProcessList {
private static final long LMKD_RECONNECT_DELAY_MS = 1000;
/**
- * The cuttoff adj for the freezer, app processes with adj greater than this value will be
- * eligible for the freezer.
- */
- static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ;
-
- /**
* Apps have no access to the private data directories of any other app, even if the other
* app has made them world-readable.
*/
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 98f738c38d63..49149e1fa415 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1699,7 +1699,7 @@ class ProcessRecord implements WindowProcessListener {
return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer()
&& !mOptRecord.isFreezeExempt()
&& !mOptRecord.shouldNotFreeze()
- && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
+ && mState.getCurAdj() >= mService.mConstants.FREEZER_CUTOFF_ADJ;
}
public void forEachConnectionHost(Consumer<ProcessRecord> consumer) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 87f87c76725e..c82933c5069e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -158,6 +158,7 @@ public class SettingsToPropertiesMapper {
"aoc",
"app_widgets",
"arc_next",
+ "art_cloud",
"art_mainline",
"art_performance",
"attack_tools",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c31b9ef60bd2..70f2a8e1dd1b 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -160,6 +160,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -176,6 +177,9 @@ import java.util.function.Consumer;
class UserController implements Handler.Callback {
private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
+ // Amount of time we wait for observers to handle onBeforeUserSwitching, before crashing system.
+ static final int DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS = 20 * 1000;
+
// Amount of time we wait for observers to handle a user switch before
// giving up on them and dismissing the user switching dialog.
static final int DEFAULT_USER_SWITCH_TIMEOUT_MS = 3 * 1000;
@@ -1920,8 +1924,14 @@ class UserController implements Handler.Callback {
return false;
}
- mHandler.post(() -> startUserInternalOnHandler(userId, oldUserId, userStartMode,
- unlockListener, callingUid, callingPid));
+ final Runnable continueStartUserInternal = () -> continueStartUserInternal(userInfo,
+ oldUserId, userStartMode, unlockListener, callingUid, callingPid);
+ if (foreground) {
+ mHandler.post(() -> dispatchOnBeforeUserSwitching(userId, () ->
+ mHandler.post(continueStartUserInternal)));
+ } else {
+ continueStartUserInternal.run();
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1929,11 +1939,11 @@ class UserController implements Handler.Callback {
return true;
}
- private void startUserInternalOnHandler(int userId, int oldUserId, int userStartMode,
+ private void continueStartUserInternal(UserInfo userInfo, int oldUserId, int userStartMode,
IProgressListener unlockListener, int callingUid, int callingPid) {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
final boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
- final UserInfo userInfo = getUserInfo(userId);
+ final int userId = userInfo.id;
boolean needStart = false;
boolean updateUmState = false;
@@ -1995,7 +2005,6 @@ class UserController implements Handler.Callback {
// it should be moved outside, but for now it's not as there are many calls to
// external components here afterwards
updateProfileRelatedCaches();
- dispatchOnBeforeUserSwitching(userId);
mInjector.getWindowManager().setCurrentUser(userId);
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
@@ -2296,25 +2305,40 @@ class UserController implements Handler.Callback {
mUserSwitchObservers.finishBroadcast();
}
- private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId) {
+ private void dispatchOnBeforeUserSwitching(@UserIdInt int newUserId, Runnable onComplete) {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("dispatchOnBeforeUserSwitching-" + newUserId);
- final int observerCount = mUserSwitchObservers.beginBroadcast();
- for (int i = 0; i < observerCount; i++) {
- final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
- t.traceBegin("onBeforeUserSwitching-" + name);
+ final AtomicBoolean isSuccessful = new AtomicBoolean(false);
+ startTimeoutForOnBeforeUserSwitching(isSuccessful);
+ informUserSwitchObservers((observer, callback) -> {
try {
- mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId);
+ observer.onBeforeUserSwitching(newUserId, callback);
} catch (RemoteException e) {
- // Ignore
- } finally {
- t.traceEnd();
+ // ignore
}
- }
- mUserSwitchObservers.finishBroadcast();
+ }, () -> {
+ isSuccessful.set(true);
+ onComplete.run();
+ }, "onBeforeUserSwitching");
t.traceEnd();
}
+ private void startTimeoutForOnBeforeUserSwitching(AtomicBoolean isSuccessful) {
+ mHandler.postDelayed(() -> {
+ if (isSuccessful.get()) {
+ return;
+ }
+ String unresponsiveObservers;
+ synchronized (mLock) {
+ unresponsiveObservers = String.join(", ", mCurWaitingUserSwitchCallbacks);
+ }
+ throw new RuntimeException("Timeout on dispatchOnBeforeUserSwitching. "
+ + "These UserSwitchObservers did not respond in "
+ + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + "ms: " + unresponsiveObservers + ".");
+ }, DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS);
+ }
+
+
/** Called on handler thread */
@VisibleForTesting
void dispatchUserSwitchComplete(@UserIdInt int oldUserId, @UserIdInt int newUserId) {
@@ -2527,70 +2551,76 @@ class UserController implements Handler.Callback {
t.traceBegin("dispatchUserSwitch-" + oldUserId + "-to-" + newUserId);
EventLog.writeEvent(EventLogTags.UC_DISPATCH_USER_SWITCH, oldUserId, newUserId);
+ uss.switching = true;
+ informUserSwitchObservers((observer, callback) -> {
+ try {
+ observer.onUserSwitching(newUserId, callback);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }, () -> {
+ synchronized (mLock) {
+ sendContinueUserSwitchLU(uss, oldUserId, newUserId);
+ }
+ }, "onUserSwitching");
+ t.traceEnd();
+ }
+ void informUserSwitchObservers(BiConsumer<IUserSwitchObserver, IRemoteCallback> consumer,
+ final Runnable onComplete, String trace) {
final int observerCount = mUserSwitchObservers.beginBroadcast();
- if (observerCount > 0) {
- final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
+ if (observerCount == 0) {
+ onComplete.run();
+ mUserSwitchObservers.finishBroadcast();
+ return;
+ }
+ final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
+ synchronized (mLock) {
+ mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
+ }
+ final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
+ final long userSwitchTimeoutMs = getUserSwitchTimeoutMs();
+ final long dispatchStartedTime = SystemClock.elapsedRealtime();
+ for (int i = 0; i < observerCount; i++) {
+ final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
+ // Prepend with unique prefix to guarantee that keys are unique
+ final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
synchronized (mLock) {
- uss.switching = true;
- mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
- }
- final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
- final long userSwitchTimeoutMs = getUserSwitchTimeoutMs();
- final long dispatchStartedTime = SystemClock.elapsedRealtime();
- for (int i = 0; i < observerCount; i++) {
- final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
- try {
- // Prepend with unique prefix to guarantee that keys are unique
- final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
+ curWaitingUserSwitchCallbacks.add(name);
+ }
+ final IRemoteCallback callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ asyncTraceEnd(trace + "-" + name, 0);
synchronized (mLock) {
- curWaitingUserSwitchCallbacks.add(name);
- }
- final IRemoteCallback callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- asyncTraceEnd("onUserSwitching-" + name, newUserId);
- synchronized (mLock) {
- long delayForObserver = SystemClock.elapsedRealtime()
- - dispatchStartedTimeForObserver;
- if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
- Slogf.w(TAG, "User switch slowed down by observer " + name
- + ": result took " + delayForObserver
- + " ms to process.");
- }
-
- long totalDelay = SystemClock.elapsedRealtime()
- - dispatchStartedTime;
- if (totalDelay > userSwitchTimeoutMs) {
- Slogf.e(TAG, "User switch timeout: observer " + name
- + "'s result was received " + totalDelay
- + " ms after dispatchUserSwitch.");
- }
-
- curWaitingUserSwitchCallbacks.remove(name);
- // Continue switching if all callbacks have been notified and
- // user switching session is still valid
- if (waitingCallbacksCount.decrementAndGet() == 0
- && (curWaitingUserSwitchCallbacks
- == mCurWaitingUserSwitchCallbacks)) {
- sendContinueUserSwitchLU(uss, oldUserId, newUserId);
- }
- }
+ long delayForObserver = SystemClock.elapsedRealtime()
+ - dispatchStartedTimeForObserver;
+ if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
+ Slogf.w(TAG, "User switch slowed down by observer " + name
+ + ": result took " + delayForObserver
+ + " ms to process. " + trace);
}
- };
- asyncTraceBegin("onUserSwitching-" + name, newUserId);
- mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
- } catch (RemoteException e) {
- // Ignore
+ long totalDelay = SystemClock.elapsedRealtime() - dispatchStartedTime;
+ if (totalDelay > userSwitchTimeoutMs) {
+ Slogf.e(TAG, "User switch timeout: observer " + name
+ + "'s result was received " + totalDelay
+ + " ms after dispatchUserSwitch. " + trace);
+ }
+ curWaitingUserSwitchCallbacks.remove(name);
+ // Continue switching if all callbacks have been notified and
+ // user switching session is still valid
+ if (waitingCallbacksCount.decrementAndGet() == 0
+ && (curWaitingUserSwitchCallbacks
+ == mCurWaitingUserSwitchCallbacks)) {
+ onComplete.run();
+ }
+ }
}
- }
- } else {
- synchronized (mLock) {
- sendContinueUserSwitchLU(uss, oldUserId, newUserId);
- }
+ };
+ asyncTraceBegin(trace + "-" + name, 0);
+ consumer.accept(mUserSwitchObservers.getBroadcastItem(i), callback);
}
mUserSwitchObservers.finishBroadcast();
- t.traceEnd(); // end dispatchUserSwitch-
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06c586f5e9c2..295e0443371d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3191,7 +3191,7 @@ public class AppOpsService extends IAppOpsService.Stub {
resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId,
Process.INVALID_UID, null, null,
Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted,
- "proxy " + message, shouldCollectMessage);
+ "proxy " + message, shouldCollectMessage, 1);
if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
@@ -3210,7 +3210,20 @@ public class AppOpsService extends IAppOpsService.Stub {
return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName,
proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, 1);
+ }
+
+ @Override
+ public void noteOperationsInBatch(Map batchedNoteOps) {
+ for (var entry : ((Map<AppOpsManager.NotedOp, Integer>) batchedNoteOps).entrySet()) {
+ AppOpsManager.NotedOp notedOp = entry.getKey();
+ int notedCount = entry.getValue();
+ mCheckOpsDelegateDispatcher.noteOperation(
+ notedOp.getOp(), notedOp.getUid(), notedOp.getPackageName(),
+ notedOp.getAttributionTag(), notedOp.getVirtualDeviceId(),
+ notedOp.getShouldCollectAsyncNotedOp(), notedOp.getMessage(),
+ notedOp.getShouldCollectMessage(), notedCount);
+ }
}
@Override
@@ -3228,7 +3241,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, 1);
}
@Override
@@ -3237,13 +3250,12 @@ public class AppOpsService extends IAppOpsService.Stub {
String message, boolean shouldCollectMessage) {
return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
+ shouldCollectMessage, 1);
}
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
- @Nullable String attributionTag, int virtualDeviceId,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage, int notedCount) {
String resolvedPackageName;
if (!shouldUseNewCheckOp()) {
verifyIncomingUid(uid);
@@ -3278,14 +3290,14 @@ public class AppOpsService extends IAppOpsService.Stub {
return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
virtualDeviceId, Process.INVALID_UID, null, null,
Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, notedCount);
}
private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag, int virtualDeviceId, int proxyUid,
String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId,
@OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3388,11 +3400,11 @@ public class AppOpsService extends IAppOpsService.Stub {
virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED);
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags);
+ getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount);
if (shouldCollectAsyncNotedOp) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
- shouldCollectMessage);
+ shouldCollectMessage, notedCount);
}
return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
@@ -3551,7 +3563,7 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode,
@Nullable String attributionTag, @OpFlags int flags, @NonNull String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
Objects.requireNonNull(message);
int callingUid = Binder.getCallingUid();
@@ -3559,42 +3571,51 @@ public class AppOpsService extends IAppOpsService.Stub {
final long token = Binder.clearCallingIdentity();
try {
synchronized (this) {
- Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
-
- RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
- AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
- attributionTag, message, System.currentTimeMillis());
- final boolean[] wasNoteForwarded = {false};
-
if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0
&& shouldCollectMessage) {
reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode,
attributionTag, message);
}
- if (callbacks != null) {
+ Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+ RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+ if (callbacks == null) {
+ return;
+ }
+
+ final boolean[] wasNoteForwarded = {false};
+ if (Flags.rateLimitBatchedNoteOpAsyncCallbacksEnabled()) {
+ notedCount = 1;
+ }
+
+ for (int i = 0; i < notedCount; i++) {
+ AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+ attributionTag, message, System.currentTimeMillis());
+ wasNoteForwarded[0] = false;
callbacks.broadcast((cb) -> {
try {
cb.opNoted(asyncNotedOp);
wasNoteForwarded[0] = true;
} catch (RemoteException e) {
Slog.e(TAG,
- "Could not forward noteOp of " + opCode + " to " + packageName
+ "Could not forward noteOp of " + opCode + " to "
+ + packageName
+ "/" + uid + "(" + attributionTag + ")", e);
}
});
- }
- if (!wasNoteForwarded[0]) {
- ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
- if (unforwardedOps == null) {
- unforwardedOps = new ArrayList<>(1);
- mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
- }
+ if (!wasNoteForwarded[0]) {
+ ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(
+ key);
+ if (unforwardedOps == null) {
+ unforwardedOps = new ArrayList<>(1);
+ mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+ }
- unforwardedOps.add(asyncNotedOp);
- if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
- unforwardedOps.remove(0);
+ unforwardedOps.add(asyncNotedOp);
+ if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) {
+ unforwardedOps.remove(0);
+ }
}
}
}
@@ -4026,7 +4047,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (shouldCollectAsyncNotedOp && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
+ message, shouldCollectMessage, 1);
}
return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
@@ -7574,34 +7595,36 @@ public class AppOpsService extends IAppOpsService.Stub {
public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- String message, boolean shouldCollectMessage) {
+ String message, boolean shouldCollectMessage, int notedCount) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, this::noteDelegateOperationImpl
+ shouldCollectMessage, notedCount, this::noteDelegateOperationImpl
);
} else {
return mPolicy.noteOperation(code, uid, packageName, attributionTag,
virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, AppOpsService.this::noteOperationImpl
+ shouldCollectMessage, notedCount, AppOpsService.this::noteOperationImpl
);
}
} else if (mCheckOpsDelegate != null) {
return noteDelegateOperationImpl(code, uid, packageName, attributionTag,
- virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ notedCount);
}
return noteOperationImpl(code, uid, packageName, attributionTag,
- virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ notedCount);
}
private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
@Nullable String packageName, @Nullable String featureId, int virtualDeviceId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
+ boolean shouldCollectMessage, int notedCount) {
return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- AppOpsService.this::noteOperationImpl
+ notedCount, AppOpsService.this::noteOperationImpl
);
}
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 314664b0a79d..4d114b4ad4ac 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -100,10 +100,12 @@ final class AttributedOp {
* @param proxyDeviceId The device Id of the proxy
* @param uidState UID state of the app noteOp/startOp was called for
* @param flags OpFlags of the call
+ * @param accessCount The number of times the op is accessed
*/
public void accessed(int proxyUid, @Nullable String proxyPackageName,
@Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
- @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) {
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ int accessCount) {
long accessTime = System.currentTimeMillis();
accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId,
uidState, flags);
@@ -111,7 +113,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
- DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
+ DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
}
/**
@@ -255,7 +257,7 @@ final class AttributedOp {
if (isStarted) {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
+ attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
}
}
@@ -451,7 +453,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), startTime, event.getAttributionFlags(),
- event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
+ event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 6b0253864e2b..5e67f26ba1f6 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -475,7 +475,7 @@ final class HistoricalRegistry {
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long accessTime,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteRegistry.AccessType int accessType) {
+ @DiscreteRegistry.AccessType int accessType, int accessCount) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -484,7 +484,7 @@ final class HistoricalRegistry {
}
getUpdatedPendingHistoricalOpsMLocked(
System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
- attributionTag, uidState, flags, 1);
+ attributionTag, uidState, flags, accessCount);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, accessTime, -1, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 4d5bce559a91..fedfe511b747 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -199,7 +199,9 @@ public final class FadeOutManager {
for (AudioPlaybackConfiguration apc : players) {
final VolumeShaper.Configuration volShaper =
mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
- fa.addFade(apc, /* skipRamp= */ false, volShaper);
+ if (volShaper != null) {
+ fa.addFade(apc, /* skipRamp= */ false, volShaper);
+ }
}
}
}
@@ -249,7 +251,7 @@ public final class FadeOutManager {
final VolumeShaper.Configuration volShaper =
mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
final FadedOutApp fa = mUidToFadedAppsMap.get(apc.getClientUid());
- if (fa == null) {
+ if (fa == null || volShaper == null) {
return;
}
fa.addFade(apc, /* skipRamp= */ true, volShaper);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index bad5b8b9567a..737820b4a788 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -119,6 +119,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.IBinder.FrozenStateChangeCallback;
import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
@@ -272,6 +273,7 @@ public final class DisplayManagerService extends SystemService {
private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
private static final int MSG_RECEIVED_DEVICE_STATE = 9;
+ private static final int MSG_DISPATCH_PENDING_PROCESS_EVENTS = 10;
private static final int[] EMPTY_ARRAY = new int[0];
private static final HdrConversionMode HDR_CONVERSION_MODE_UNSUPPORTED = new HdrConversionMode(
HDR_CONVERSION_UNSUPPORTED);
@@ -286,7 +288,6 @@ public final class DisplayManagerService extends SystemService {
private InputManagerInternal mInputManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
- private final DisplayFrozenProcessListener mDisplayFrozenProcessListener;
@Nullable
private IMediaProjectionManager mProjectionService;
@@ -630,7 +631,6 @@ public final class DisplayManagerService extends SystemService {
mFlags = injector.getFlags();
mHandler = new DisplayManagerHandler(displayThreadLooper);
mHandlerExecutor = new HandlerExecutor(mHandler);
- mDisplayFrozenProcessListener = new DisplayFrozenProcessListener();
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -1165,31 +1165,11 @@ public final class DisplayManagerService extends SystemService {
}
}
- private class DisplayFrozenProcessListener
- implements ActivityManagerInternal.FrozenProcessListener {
- public void onProcessFrozen(int pid) {
- synchronized (mSyncRoot) {
- CallbackRecord callback = mCallbacks.get(pid);
- if (callback == null) {
- return;
- }
- callback.setFrozen(true);
- }
- }
-
- public void onProcessUnfrozen(int pid) {
- // First, see if there is a callback associated with this pid. If there's no
- // callback, then there is nothing to do.
- CallbackRecord callback;
- synchronized (mSyncRoot) {
- callback = mCallbacks.get(pid);
- if (callback == null) {
- return;
- }
- callback.setFrozen(false);
- }
- // Attempt to dispatch pending events if the process is coming out of frozen.
+ private void dispatchPendingProcessEvents(@NonNull Object cb) {
+ if (cb instanceof CallbackRecord callback) {
callback.dispatchPending();
+ } else {
+ Slog.wtf(TAG, "not a callback: " + cb);
}
}
@@ -2397,9 +2377,13 @@ public final class DisplayManagerService extends SystemService {
// We don't bother invalidating the display info caches here because any changes to the
// display info will trigger a cache invalidation inside of LogicalDisplay before we hit
// this point.
- sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
applyDisplayChangedLocked(display);
+
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.onDisplayChanged(display.getDisplayInfoLocked());
+ }
}
private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -2643,7 +2627,8 @@ public final class DisplayManagerService extends SystemService {
private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) {
if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) {
- sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ sendDisplayEventIfEnabledLocked(display,
+ DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
}
}
@@ -3474,7 +3459,7 @@ public final class DisplayManagerService extends SystemService {
private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
- displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
mHandler.sendMessage(msg);
}
@@ -4047,6 +4032,9 @@ public final class DisplayManagerService extends SystemService {
deliverDisplayGroupEvent(msg.arg1, msg.arg2);
break;
+ case MSG_DISPATCH_PENDING_PROCESS_EVENTS:
+ dispatchPendingProcessEvents(msg.obj);
+ break;
}
}
}
@@ -4061,7 +4049,7 @@ public final class DisplayManagerService extends SystemService {
handleLogicalDisplayAddedLocked(display);
break;
- case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED:
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_BASIC_CHANGED:
handleLogicalDisplayChangedLocked(display);
break;
@@ -4114,7 +4102,7 @@ public final class DisplayManagerService extends SystemService {
}
}
- private final class CallbackRecord implements DeathRecipient {
+ private final class CallbackRecord implements DeathRecipient, FrozenStateChangeCallback {
public final int mPid;
public final int mUid;
private final IDisplayManagerCallback mCallback;
@@ -4137,6 +4125,8 @@ public final class DisplayManagerService extends SystemService {
private boolean mCached;
@GuardedBy("mCallback")
private boolean mFrozen;
+ @GuardedBy("mCallback")
+ private boolean mAlive;
CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
@InternalEventFlag long internalEventFlagsMask) {
@@ -4146,18 +4136,20 @@ public final class DisplayManagerService extends SystemService {
mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask);
mCached = false;
mFrozen = false;
+ mAlive = true;
if (deferDisplayEventsWhenFrozen()) {
- // Some CallbackRecords are registered very early in system boot, before
- // mActivityManagerInternal is initialized. If mActivityManagerInternal is null,
- // do not register the frozen process listener. However, do verify that all such
- // registrations are for the self pid (which can never be frozen, so the frozen
- // process listener does not matter).
- if (mActivityManagerInternal != null) {
- mActivityManagerInternal.addFrozenProcessListener(pid, mHandlerExecutor,
- mDisplayFrozenProcessListener);
- } else if (Process.myPid() != pid) {
- Slog.e(TAG, "DisplayListener registered too early");
+ try {
+ callback.asBinder().addFrozenStateChangeCallback(this);
+ } catch (UnsupportedOperationException e) {
+ // Ignore the exception. The callback is not supported on this platform or on
+ // this binder. The callback is never supported for local binders. There is
+ // no error: the UID importance listener will still operate. A log message is
+ // provided for debug.
+ Slog.v(TAG, "FrozenStateChangeCallback not supported for pid " + mPid);
+ } catch (RemoteException e) {
+ // This is unexpected. Just give up.
+ throw new RuntimeException(e);
}
}
@@ -4182,7 +4174,7 @@ public final class DisplayManagerService extends SystemService {
*/
@GuardedBy("mCallback")
private boolean hasPendingAndIsReadyLocked() {
- return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty();
+ return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive;
}
/**
@@ -4190,7 +4182,7 @@ public final class DisplayManagerService extends SystemService {
* receive events and there are pending events to be delivered.
* This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
*/
- public boolean setFrozen(boolean frozen) {
+ private boolean setFrozen(boolean frozen) {
synchronized (mCallback) {
mFrozen = frozen;
return hasPendingAndIsReadyLocked();
@@ -4211,6 +4203,9 @@ public final class DisplayManagerService extends SystemService {
@Override
public void binderDied() {
+ synchronized (mCallback) {
+ mAlive = false;
+ }
if (DEBUG || extraLogging(mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
@@ -4221,6 +4216,14 @@ public final class DisplayManagerService extends SystemService {
onCallbackDied(this);
}
+ @Override
+ public void onFrozenStateChanged(@NonNull IBinder who, int state) {
+ if (setFrozen(state == FrozenStateChangeCallback.STATE_FROZEN)) {
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_PENDING_PROCESS_EVENTS, this);
+ mHandler.sendMessage(msg);
+ }
+ }
+
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
* success. This returns true even if the event was deferred because the remote client is
@@ -4286,8 +4289,9 @@ public final class DisplayManagerService extends SystemService {
switch (event) {
case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0;
- case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
- return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0;
+ case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED:
+ return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED)
+ != 0;
case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
return (mask
& DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)
@@ -4386,7 +4390,7 @@ public final class DisplayManagerService extends SystemService {
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty()) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
return true;
}
if (!isReadyLocked()) {
@@ -4542,7 +4546,8 @@ public final class DisplayManagerService extends SystemService {
public void registerCallback(IDisplayManagerCallback callback) {
registerCallbackWithEventMask(callback,
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED);
}
@@ -6057,6 +6062,7 @@ public final class DisplayManagerService extends SystemService {
* Return the value of the pause
*/
private static boolean deferDisplayEventsWhenFrozen() {
- return com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
+ return android.os.Flags.binderFrozenStateChangeCallback()
+ && com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index 5b78726cc421..461a9f3f2a0d 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -85,13 +85,26 @@ class DisplayTopologyCoordinator {
}
/**
+ * Update the topology with display changes.
+ * @param info The new display info
+ */
+ void onDisplayChanged(DisplayInfo info) {
+ synchronized (mSyncRoot) {
+ if (mTopology.updateDisplay(info.displayId, getWidth(info), getHeight(info))) {
+ sendTopologyUpdateLocked();
+ }
+ }
+ }
+
+ /**
* Remove a display from the topology.
* @param displayId The logical display ID
*/
void onDisplayRemoved(int displayId) {
synchronized (mSyncRoot) {
- mTopology.removeDisplay(displayId);
- sendTopologyUpdateLocked();
+ if (mTopology.removeDisplay(displayId)) {
+ sendTopologyUpdateLocked();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 79592a656409..006921572977 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -81,7 +81,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public static final int LOGICAL_DISPLAY_EVENT_BASE = 0;
public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0;
- public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1;
+ public static final int LOGICAL_DISPLAY_EVENT_BASIC_CHANGED = 1 << 1;
public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2;
public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3;
public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4;
@@ -172,9 +172,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
/**
* Has an entry for every logical display that the rest of the system has been notified about.
- * Any entry in here requires us to send a {@link LOGICAL_DISPLAY_EVENT_REMOVED} event when it
- * is deleted or {@link LOGICAL_DISPLAY_EVENT_CHANGED} when it is changed. The values are any
- * of the {@code UPDATE_STATE_*} constant types.
+ * The values are any of the {@code UPDATE_STATE_*} constant types.
*/
private final SparseIntArray mUpdatedLogicalDisplays = new SparseIntArray();
@@ -811,7 +809,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final boolean isCurrentlyEnabled = display.isEnabledLocked();
int logicalDisplayEventMask = mLogicalDisplaysToUpdate
.get(displayId, LOGICAL_DISPLAY_EVENT_BASE);
-
+ boolean hasBasicInfoChanged =
+ !mTempDisplayInfo.equals(newDisplayInfo, /* compareRefreshRate */ false);
// The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) {
// Remove from group
@@ -863,19 +862,28 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
LOGICAL_DISPLAY_EVENT_REMOVED;
logicalDisplayEventMask |= event;
- } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
+ } else if (wasDirty) {
// If only the hdr/sdr ratio changed, then send just the event for that case
if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
} else {
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
+ | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED
+ | LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
}
+ } else if (hasBasicInfoChanged
+ || mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) {
+ // If only the hdr/sdr ratio changed, then send just the event for that case
+ if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
+ } else {
- if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) {
+ if (hasBasicInfoChanged) {
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED;
+ }
logicalDisplayEventMask
|= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo);
}
-
// The display is involved in a display layout transition
} else if (updateState == UPDATE_STATE_TRANSITION) {
logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION;
@@ -891,7 +899,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// things like display cutouts.
display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
+ logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_BASIC_CHANGED
+ | LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
}
}
mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
@@ -930,7 +939,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (mFlags.isConnectedDisplayManagementEnabled()) {
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
}
- sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+ sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
@@ -962,7 +971,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
}
- if (mTempDisplayInfo.state != newDisplayInfo.state) {
+ if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()
+ && mTempDisplayInfo.state != newDisplayInfo.state) {
mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
}
return mask;
@@ -1357,8 +1367,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return "added";
case LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION:
return "transition";
- case LOGICAL_DISPLAY_EVENT_CHANGED:
- return "changed";
case LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED:
return "framerate_override";
case LOGICAL_DISPLAY_EVENT_SWAPPED:
@@ -1375,6 +1383,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
return "state_changed";
case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
return "refresh_rate_changed";
+ case LOGICAL_DISPLAY_EVENT_BASIC_CHANGED:
+ return "basic_changed";
}
return null;
}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java
new file mode 100644
index 000000000000..6cab60c05b8e
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyGnssAssistanceProvider.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider.proxy;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.GnssAssistanceProviderBase;
+import android.location.provider.IGnssAssistanceCallback;
+import android.location.provider.IGnssAssistanceProvider;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IGnssAssitanceProvider implementations.
+ */
+public class ProxyGnssAssistanceProvider {
+
+ private static final String TAG = "GnssAssistanceProxy";
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyGnssAssistanceProvider createAndRegister(Context context) {
+ ProxyGnssAssistanceProvider proxy = new ProxyGnssAssistanceProvider(context);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private final ServiceWatcher mServiceWatcher;
+
+ private ProxyGnssAssistanceProvider(Context context) {
+ mServiceWatcher =
+ ServiceWatcher.create(
+ context,
+ TAG,
+ CurrentUserServiceSupplier.createFromConfig(
+ context,
+ GnssAssistanceProviderBase.ACTION_GNSS_ASSISTANCE_PROVIDER,
+ com.android.internal.R.bool.config_enableGnssAssistanceOverlay,
+ com.android.internal.R.string
+ .config_gnssAssistanceProviderPackageName),
+ /* serviceListener= */ null);
+ }
+
+ private boolean register() {
+ boolean resolves = mServiceWatcher.checkServiceResolves();
+ if (resolves) {
+ mServiceWatcher.register();
+ }
+ return resolves;
+ }
+
+ /**
+ * Request GNSS assistance.
+ */
+ public void request(IGnssAssistanceCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IGnssAssistanceProvider.Stub.asInterface(binder).request(callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ Log.w(TAG, "Error on requesting GnssAssistance: " + t);
+ callback.onError();
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 58c8450d714d..0d6e502cf965 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
+import android.media.MediaRoute2ProviderService.Reason;
import android.media.MediaRouter2;
import android.media.MediaRouter2Utils;
import android.media.RouteDiscoveryPreference;
@@ -123,6 +124,13 @@ abstract class MediaRoute2Provider {
}
}
+ /** Calls {@link Callback#onRequestFailed} with the given id and reason. */
+ protected void notifyRequestFailed(long requestId, @Reason int reason) {
+ if (mCallback != null) {
+ mCallback.onRequestFailed(/* provider= */ this, requestId, reason);
+ }
+ }
+
void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
setProviderState(providerInfo);
notifyProviderState();
@@ -171,11 +179,34 @@ abstract class MediaRoute2Provider {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
long requestId, @Nullable RoutingSessionInfo sessionInfo);
- void onSessionUpdated(@NonNull MediaRoute2Provider provider,
- @NonNull RoutingSessionInfo sessionInfo);
+
+ /**
+ * Called when there's a session info change.
+ *
+ * <p>If the provided {@code sessionInfo} has a null {@link
+ * RoutingSessionInfo#getClientPackageName()}, that means that it's applicable to all
+ * packages. We call this type of routing session "global". This is typically used for
+ * system provided {@link RoutingSessionInfo}. However, some applications may be exempted
+ * from the global routing sessions, because their media is being routed using a session
+ * different from the global routing session.
+ *
+ * @param provider The provider that owns the session that changed.
+ * @param sessionInfo The new {@link RoutingSessionInfo}.
+ * @param packageNamesWithRoutingSessionOverrides The names of packages that are not
+ * affected by global session changes. This set may only be non-empty when the {@code
+ * sessionInfo} is for the global session, and therefore has no {@link
+ * RoutingSessionInfo#getClientPackageName()}.
+ */
+ void onSessionUpdated(
+ @NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo,
+ Set<String> packageNamesWithRoutingSessionOverrides);
+
void onSessionReleased(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo);
- void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason);
+
+ void onRequestFailed(
+ @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason);
}
/**
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index f09be2c15ee0..d6f7d3bdd4a4 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -31,6 +32,7 @@ import android.media.IMediaRoute2ProviderServiceCallback;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.MediaRoute2ProviderService.Reason;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
@@ -41,6 +43,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -89,6 +92,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
mRequestIdToSessionCreationRequest;
@GuardedBy("mLock")
+ private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks;
+
+ @GuardedBy("mLock")
+ private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest;
+
+ @GuardedBy("mLock")
private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest;
MediaRoute2ProviderServiceProxy(
@@ -102,6 +111,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
mContext = Objects.requireNonNull(context, "Context must not be null.");
mRequestIdToSessionCreationRequest = new LongSparseArray<>();
mSessionOriginalIdToTransferRequest = new HashMap<>();
+ mRequestIdToSystemSessionRequest = new LongSparseArray<>();
+ mSystemSessionCallbacks = new ArrayMap<>();
mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
mSupportsSystemMediaRouting = supportsSystemMediaRouting;
mUserId = userId;
@@ -236,6 +247,48 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
}
}
+ /**
+ * Requests the creation of a system media routing session.
+ *
+ * @param requestId The id of the request.
+ * @param uid The uid of the package whose media to route, or {@link
+ * android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media
+ * must be routed).
+ * @param packageName The package name to populate {@link
+ * RoutingSessionInfo#getClientPackageName()}.
+ * @param routeId The id of the route to be initially {@link
+ * RoutingSessionInfo#getSelectedRoutes()}.
+ * @param sessionHints An optional bundle with paramets.
+ * @param callback A {@link SystemMediaSessionCallback} to notify of session events.
+ * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+ */
+ public void requestCreateSystemMediaSession(
+ long requestId,
+ int uid,
+ String packageName,
+ String routeId,
+ @Nullable Bundle sessionHints,
+ @NonNull SystemMediaSessionCallback callback) {
+ if (!Flags.enableMirroringInMediaRouter2()) {
+ throw new IllegalStateException(
+ "Unexpected call to requestCreateSystemMediaSession. Governing flag is"
+ + " disabled.");
+ }
+ if (mConnectionReady) {
+ boolean binderRequestSucceeded =
+ mActiveConnection.requestCreateSystemMediaSession(
+ requestId, uid, packageName, routeId, sessionHints);
+ if (!binderRequestSucceeded) {
+ // notify failure.
+ return;
+ }
+ updateBinding();
+ synchronized (mLock) {
+ mRequestIdToSystemSessionRequest.put(requestId, callback);
+ }
+ }
+ }
+
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
@@ -292,7 +345,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
mLastDiscoveryPreference != null
&& mLastDiscoveryPreference.shouldPerformActiveScan()
&& mSupportsSystemMediaRouting;
+ boolean bindDueToOngoingSystemMediaRoutingSessions = false;
+ if (Flags.enableMirroringInMediaRouter2()) {
+ synchronized (mLock) {
+ bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty();
+ }
+ }
if (!getSessionInfos().isEmpty()
+ || bindDueToOngoingSystemMediaRoutingSessions
|| bindDueToManagerScan
|| bindDueToSystemMediaRoutingSupport) {
return true;
@@ -438,6 +498,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
String newSessionId = newSession.getId();
synchronized (mLock) {
+ var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId);
+ if (systemMediaSessionCallback != null) {
+ mRequestIdToSystemSessionRequest.remove(requestId);
+ mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback);
+ systemMediaSessionCallback.onSessionUpdate(newSession);
+ return;
+ }
+
if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
newSession =
createSessionWithPopulatedTransferInitiationDataLocked(
@@ -569,6 +637,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
boolean found = false;
synchronized (mLock) {
+ var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId());
+ if (sessionCallback != null) {
+ sessionCallback.onSessionReleased();
+ return;
+ }
+
mSessionOriginalIdToTransferRequest.remove(releasedSession.getId());
for (RoutingSessionInfo session : mSessionInfos) {
if (TextUtils.equals(session.getId(), releasedSession.getId())) {
@@ -602,7 +676,11 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
private void dispatchSessionUpdated(RoutingSessionInfo session) {
mHandler.sendMessage(
- obtainMessage(mCallback::onSessionUpdated, this, session));
+ obtainMessage(
+ mCallback::onSessionUpdated,
+ this,
+ session,
+ /* packageNamesWithRoutingSessionOverrides= */ Set.of()));
}
private void dispatchSessionReleased(RoutingSessionInfo session) {
@@ -645,6 +723,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
for (RoutingSessionInfo sessionInfo : mSessionInfos) {
mCallback.onSessionReleased(this, sessionInfo);
}
+ if (Flags.enableMirroringInMediaRouter2()) {
+ for (var callback : mSystemSessionCallbacks.values()) {
+ callback.onSessionReleased();
+ }
+ mSystemSessionCallbacks.clear();
+ int requestsSize = mRequestIdToSystemSessionRequest.size();
+ for (int i = 0; i < requestsSize; i++) {
+ var callback = mRequestIdToSystemSessionRequest.valueAt(i);
+ var requestId = mRequestIdToSystemSessionRequest.keyAt(i);
+ callback.onRequestFailed(requestId, REASON_REJECTED);
+ }
+ mSystemSessionCallbacks.clear();
+ }
mSessionInfos.clear();
mReleasingSessions.clear();
mRequestIdToSessionCreationRequest.clear();
@@ -673,6 +764,26 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
pendingTransferCount);
}
+ /**
+ * Callback for events related to system media sessions.
+ *
+ * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+ */
+ public interface SystemMediaSessionCallback {
+
+ /**
+ * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation
+ * of the given session info.
+ */
+ void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo);
+
+ /** Called when the request with the given id fails for the given reason. */
+ void onRequestFailed(long requestId, @Reason int reason);
+
+ /** Called when the corresponding session is released. */
+ void onSessionReleased();
+ }
+
// All methods in this class are called on the main thread.
private final class ServiceConnectionImpl implements ServiceConnection {
@@ -739,6 +850,28 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
}
}
+ /**
+ * Sends a system media session creation request to the provider service, and returns
+ * whether the request transaction succeeded.
+ *
+ * <p>The transaction might fail, for example, if the recipient process has died.
+ */
+ public boolean requestCreateSystemMediaSession(
+ long requestId,
+ int uid,
+ String packageName,
+ String routeId,
+ @Nullable Bundle sessionHints) {
+ try {
+ mService.requestCreateSystemMediaSession(
+ requestId, uid, packageName, routeId, sessionHints);
+ return true;
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request.");
+ }
+ return false;
+ }
+
public void releaseSession(long requestId, String sessionId) {
try {
mService.releaseSession(requestId, sessionId);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 58deffcbd4ba..5e6737a485af 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -846,33 +846,29 @@ class MediaRouter2ServiceImpl {
try {
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- List<RoutingSessionInfo> sessionInfos;
+ SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider();
if (hasSystemRoutingPermissions) {
- if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) {
+ if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
- return userRecord.mHandler.getSystemProvider()
- .generateDeviceRouteSelectedSessionInfo(targetPackageName);
+ return systemProvider.generateDeviceRouteSelectedSessionInfo(
+ targetPackageName);
} else {
- sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
- if (!sessionInfos.isEmpty()) {
- // Return a copy of the current system session with no modification,
- // except setting the client package name.
- return new RoutingSessionInfo.Builder(sessionInfos.get(0))
- .setClientPackageName(targetPackageName)
- .build();
+ RoutingSessionInfo session =
+ systemProvider.getSessionForPackage(targetPackageName);
+ if (session != null) {
+ return session;
} else {
Slog.w(TAG, "System provider does not have any session info.");
+ return null;
}
}
} else {
- return new RoutingSessionInfo.Builder(
- userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
+ return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo())
.setClientPackageName(targetPackageName)
.build();
}
}
- return null;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2638,10 +2634,17 @@ class MediaRouter2ServiceImpl {
}
@Override
- public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
- @NonNull RoutingSessionInfo sessionInfo) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
- this, provider, sessionInfo));
+ public void onSessionUpdated(
+ @NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo,
+ Set<String> packageNamesWithRoutingSessionOverrides) {
+ sendMessage(
+ PooledLambda.obtainMessage(
+ UserHandler::onSessionInfoChangedOnHandler,
+ this,
+ provider,
+ sessionInfo,
+ packageNamesWithRoutingSessionOverrides));
}
@Override
@@ -3152,10 +3155,31 @@ class MediaRouter2ServiceImpl {
toOriginalRequestId(uniqueRequestId), sessionInfo);
}
- private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
- @NonNull RoutingSessionInfo sessionInfo) {
+ /**
+ * Implementation of {@link MediaRoute2Provider.Callback#onSessionUpdated}.
+ *
+ * <p>Must run on the thread that corresponds to this {@link UserHandler}.
+ */
+ private void onSessionInfoChangedOnHandler(
+ @NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo,
+ Set<String> packageNamesWithRoutingSessionOverrides) {
List<ManagerRecord> managers = getManagerRecords();
for (ManagerRecord manager : managers) {
+ if (Flags.enableMirroringInMediaRouter2()) {
+ String targetPackageName = manager.mTargetPackageName;
+ boolean skipDueToOverride =
+ targetPackageName != null
+ && packageNamesWithRoutingSessionOverrides.contains(
+ targetPackageName);
+ boolean sessionIsForTargetPackage =
+ TextUtils.isEmpty(sessionInfo.getClientPackageName()) // is global.
+ || TextUtils.equals(
+ targetPackageName, sessionInfo.getClientPackageName());
+ if (skipDueToOverride || !sessionIsForTargetPackage) {
+ continue;
+ }
+ }
manager.notifySessionUpdated(sessionInfo);
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index b93846bf9ee7..60fced1e0c51 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -62,7 +62,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
private final AudioManager mAudioManager;
- private final Handler mHandler;
+ protected final Handler mHandler;
private final Context mContext;
private final UserHandle mUser;
@@ -116,7 +116,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
() -> {
publishProviderState();
if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
});
@@ -129,7 +129,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
() -> {
publishProviderState();
if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
}));
}
@@ -161,7 +161,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
public void setCallback(Callback callback) {
super.setCallback(callback);
notifyProviderState();
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
@Override
@@ -296,7 +296,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
&& updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
}
@@ -327,6 +327,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
/**
+ * Returns the {@link RoutingSessionInfo} that corresponds to the package with the given name.
+ */
+ public RoutingSessionInfo getSessionForPackage(String targetPackageName) {
+ synchronized (mLock) {
+ if (!mSessionInfos.isEmpty()) {
+ // Return a copy of the current system session with no modification,
+ // except setting the client package name.
+ return new RoutingSessionInfo.Builder(mSessionInfos.get(0))
+ .setClientPackageName(targetPackageName)
+ .build();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
* Builds a system {@link RoutingSessionInfo} with the selected route set to the currently
* selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes
* set to the currently available (connected) bluetooth routes.
@@ -626,20 +643,21 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
notifyProviderState();
}
- void notifySessionInfoUpdated() {
+ void notifyGlobalSessionInfoUpdated() {
if (mCallback == null) {
return;
}
RoutingSessionInfo sessionInfo;
synchronized (mLock) {
- sessionInfo = mSessionInfos.get(0);
- if (sessionInfo == null) {
+ if (mSessionInfos.isEmpty()) {
return;
}
+ sessionInfo = mSessionInfos.get(0);
}
- mCallback.onSessionUpdated(this, sessionInfo);
+ mCallback.onSessionUpdated(
+ this, sessionInfo, /* packageNamesWithRoutingSessionOverrides= */ Set.of());
}
@Override
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 7dc30ab66fd2..8931e3a1426e 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -18,23 +18,33 @@ package com.android.server.media;
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.MediaRoute2ProviderService.Reason;
+import android.media.MediaRouter2Utils;
import android.media.RoutingSessionInfo;
+import android.os.Binder;
import android.os.Looper;
+import android.os.Process;
import android.os.UserHandle;
-import android.util.ArraySet;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.LongSparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.media.MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Stream;
/**
@@ -48,11 +58,33 @@ import java.util.stream.Stream;
private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM";
private static final String ROUTE_ID_SYSTEM_SEPARATOR = ".";
+ private final PackageManager mPackageManager;
+
@GuardedBy("mLock")
private MediaRoute2ProviderInfo mLastSystemProviderInfo;
@GuardedBy("mLock")
- private final Map<String, ProviderProxyRecord> mProxyRecords = new HashMap<>();
+ private final Map<String, ProviderProxyRecord> mProxyRecords = new ArrayMap<>();
+
+ /**
+ * Maps package names to corresponding sessions maintained by {@link MediaRoute2ProviderService
+ * provider services}.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, SystemMediaSessionRecord> mPackageNameToSessionRecord =
+ new ArrayMap<>();
+
+ /**
+ * Maps route {@link MediaRoute2Info#getOriginalId original ids} to the id of the {@link
+ * MediaRoute2ProviderService provider service} that manages the corresponding route.
+ */
+ @GuardedBy("mLock")
+ private final Map<String, String> mOriginalRouteIdToProviderId = new ArrayMap<>();
+
+ /** Maps request ids to pending session creation callbacks. */
+ @GuardedBy("mLock")
+ private final LongSparseArray<SystemMediaSessionCallbackImpl> mPendingSessionCreations =
+ new LongSparseArray<>();
private static final ComponentName COMPONENT_NAME =
new ComponentName(
@@ -69,6 +101,128 @@ import java.util.stream.Stream;
private SystemMediaRoute2Provider2(Context context, UserHandle user, Looper looper) {
super(context, COMPONENT_NAME, user, looper);
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public void transferToRoute(
+ long requestId,
+ @NonNull UserHandle clientUserHandle,
+ @NonNull String clientPackageName,
+ String sessionOriginalId,
+ String routeOriginalId,
+ int transferReason) {
+ synchronized (mLock) {
+ var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId);
+ var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId);
+ // Holds the target route, if it's managed by a provider service. Holds null otherwise.
+ var serviceTargetRoute =
+ targetProviderProxyRecord != null
+ ? targetProviderProxyRecord.getRouteByOriginalId(routeOriginalId)
+ : null;
+ var existingSessionRecord = mPackageNameToSessionRecord.get(clientPackageName);
+ if (existingSessionRecord != null) {
+ var existingSession = existingSessionRecord.mSourceSessionInfo;
+ if (targetProviderProxyId != null
+ && TextUtils.equals(
+ targetProviderProxyId, existingSession.getProviderId())) {
+ // The currently selected route and target route both belong to the same
+ // provider. We tell the provider to handle the transfer.
+ targetProviderProxyRecord.requestTransfer(
+ existingSession.getOriginalId(), serviceTargetRoute);
+ } else {
+ // The target route is handled by a provider other than the target one. We need
+ // to release the existing session.
+ var currentProxyRecord = existingSessionRecord.getProxyRecord();
+ if (currentProxyRecord != null) {
+ currentProxyRecord.releaseSession(
+ requestId, existingSession.getOriginalId());
+ existingSessionRecord.removeSelfFromSessionMap();
+ }
+ }
+ }
+
+ if (serviceTargetRoute != null) {
+ boolean isGlobalSession = TextUtils.isEmpty(clientPackageName);
+ int uid;
+ if (isGlobalSession) {
+ uid = Process.INVALID_UID;
+ } else {
+ uid = fetchUid(clientPackageName, clientUserHandle);
+ if (uid == Process.INVALID_UID) {
+ throw new IllegalArgumentException(
+ "Cannot resolve transfer for "
+ + clientPackageName
+ + " and "
+ + clientUserHandle);
+ }
+ }
+ var pendingCreationCallback =
+ new SystemMediaSessionCallbackImpl(
+ targetProviderProxyId, requestId, clientPackageName);
+ mPendingSessionCreations.put(requestId, pendingCreationCallback);
+ targetProviderProxyRecord.requestCreateSystemMediaSession(
+ requestId,
+ uid,
+ clientPackageName,
+ routeOriginalId,
+ pendingCreationCallback);
+ } else {
+ // The target route is not provided by any of the services. Assume it's a system
+ // provided route.
+ super.transferToRoute(
+ requestId,
+ clientUserHandle,
+ clientPackageName,
+ sessionOriginalId,
+ routeOriginalId,
+ transferReason);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public RoutingSessionInfo getSessionForPackage(String packageName) {
+ synchronized (mLock) {
+ var systemSession = super.getSessionForPackage(packageName);
+ if (systemSession == null) {
+ return null;
+ }
+ var overridingSession = mPackageNameToSessionRecord.get(packageName);
+ if (overridingSession != null) {
+ var builder =
+ new RoutingSessionInfo.Builder(overridingSession.mTranslatedSessionInfo)
+ .setProviderId(mUniqueId)
+ .setSystemSession(true);
+ for (var systemRoute : mLastSystemProviderInfo.getRoutes()) {
+ builder.addTransferableRoute(systemRoute.getOriginalId());
+ }
+ return builder.build();
+ } else {
+ return systemSession;
+ }
+ }
+ }
+
+ /**
+ * Returns the uid that corresponds to the given name and user handle, or {@link
+ * Process#INVALID_UID} if a uid couldn't be found.
+ */
+ @SuppressLint("MissingPermission")
+ // We clear the calling identity before calling the package manager, and we are running on the
+ // system_server.
+ private int fetchUid(String clientPackageName, UserHandle clientUserHandle) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mPackageManager.getApplicationInfoAsUser(
+ clientPackageName, /* flags= */ 0, clientUserHandle)
+ .uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -85,21 +239,21 @@ import java.util.stream.Stream;
} else {
mProxyRecords.put(serviceProxy.mUniqueId, proxyRecord);
}
- setProviderState(buildProviderInfo());
+ updateProviderInfo();
}
updateSessionInfo();
notifyProviderState();
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
@Override
public void onSystemProviderRoutesChanged(MediaRoute2ProviderInfo providerInfo) {
synchronized (mLock) {
mLastSystemProviderInfo = providerInfo;
- setProviderState(buildProviderInfo());
+ updateProviderInfo();
}
updateSessionInfo();
- notifySessionInfoUpdated();
+ notifyGlobalSessionInfoUpdated();
}
/**
@@ -116,10 +270,13 @@ import java.util.stream.Stream;
var builder = new RoutingSessionInfo.Builder(systemSessionInfo);
mProxyRecords.values().stream()
.flatMap(ProviderProxyRecord::getRoutesStream)
- .map(MediaRoute2Info::getId)
+ .map(MediaRoute2Info::getOriginalId)
.forEach(builder::addTransferableRoute);
mSessionInfos.clear();
mSessionInfos.add(builder.build());
+ for (var sessionRecords : mPackageNameToSessionRecord.values()) {
+ mSessionInfos.add(sessionRecords.mTranslatedSessionInfo);
+ }
}
}
@@ -129,13 +286,84 @@ import java.util.stream.Stream;
* provider services}.
*/
@GuardedBy("mLock")
- private MediaRoute2ProviderInfo buildProviderInfo() {
+ private void updateProviderInfo() {
MediaRoute2ProviderInfo.Builder builder =
new MediaRoute2ProviderInfo.Builder(mLastSystemProviderInfo);
- mProxyRecords.values().stream()
- .flatMap(ProviderProxyRecord::getRoutesStream)
- .forEach(builder::addRoute);
- return builder.build();
+ mOriginalRouteIdToProviderId.clear();
+ for (var proxyRecord : mProxyRecords.values()) {
+ String proxyId = proxyRecord.mProxy.mUniqueId;
+ proxyRecord
+ .getRoutesStream()
+ .forEach(
+ route -> {
+ builder.addRoute(route);
+ mOriginalRouteIdToProviderId.put(route.getOriginalId(), proxyId);
+ });
+ }
+ setProviderState(builder.build());
+ }
+
+ @Override
+ /* package */ void notifyGlobalSessionInfoUpdated() {
+ if (mCallback == null) {
+ return;
+ }
+
+ RoutingSessionInfo sessionInfo;
+ Set<String> packageNamesWithRoutingSessionOverrides;
+ synchronized (mLock) {
+ if (mSessionInfos.isEmpty()) {
+ return;
+ }
+ packageNamesWithRoutingSessionOverrides = mPackageNameToSessionRecord.keySet();
+ sessionInfo = mSessionInfos.getFirst();
+ }
+
+ mCallback.onSessionUpdated(this, sessionInfo, packageNamesWithRoutingSessionOverrides);
+ }
+
+ private void onSessionOverrideUpdated(RoutingSessionInfo sessionInfo) {
+ // TODO: b/362507305 - Consider adding routes from other provider services. This is not a
+ // trivial change because a provider1-route to provider2-route transfer has seemingly two
+ // possible approachies. Either we first release the current session and then create the new
+ // one, in which case the audio is briefly going to leak through the system route. On the
+ // other hand, if we first create the provider2 session, then there will be a period during
+ // which there will be two overlapping routing policies asking for the exact same media
+ // stream.
+ var builder = new RoutingSessionInfo.Builder(sessionInfo);
+ mLastSystemProviderInfo.getRoutes().stream()
+ .map(MediaRoute2Info::getOriginalId)
+ .forEach(builder::addTransferableRoute);
+ mCallback.onSessionUpdated(
+ /* provider= */ this,
+ builder.build(),
+ /* packageNamesWithRoutingSessionOverrides= */ Set.of());
+ }
+
+ /**
+ * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a
+ * original id.
+ */
+ private static String uniqueIdAsSystemRouteId(String providerId, String uniqueRouteId) {
+ return asSystemRouteId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId));
+ }
+
+ /**
+ * Returns a unique {@link MediaRoute2Info#getOriginalId() original id} for this provider to
+ * publish system media routes from {@link MediaRoute2ProviderService provider services}.
+ *
+ * <p>This provider will publish system media routes as part of the system routing session.
+ * However, said routes may also support {@link MediaRoute2Info#FLAG_ROUTING_TYPE_REMOTE remote
+ * routing}, meaning we cannot use the same id, or there would be an id collision. As a result,
+ * we derive a {@link MediaRoute2Info#getOriginalId original id} that is unique among all
+ * original route ids used by this provider.
+ */
+ private static String asSystemRouteId(String providerId, String originalRouteId) {
+ return ROUTE_ID_PREFIX_SYSTEM
+ + ROUTE_ID_SYSTEM_SEPARATOR
+ + providerId
+ + ROUTE_ID_SYSTEM_SEPARATOR
+ + originalRouteId;
}
/**
@@ -145,14 +373,69 @@ import java.util.stream.Stream;
* @param mProxy The corresponding {@link MediaRoute2ProviderServiceProxy}.
* @param mSystemMediaRoutes The last snapshot of routes from the service that support system
* media routing, as defined by {@link MediaRoute2Info#supportsSystemMediaRouting()}.
+ * @param mNewOriginalIdToSourceOriginalIdMap Maps the {@link #mSystemMediaRoutes} ids to the
+ * original ids of corresponding {@link MediaRoute2ProviderService service} route.
*/
private record ProviderProxyRecord(
MediaRoute2ProviderServiceProxy mProxy,
- Collection<MediaRoute2Info> mSystemMediaRoutes) {
+ Map<String, MediaRoute2Info> mSystemMediaRoutes,
+ Map<String, String> mNewOriginalIdToSourceOriginalIdMap) {
/** Returns a stream representation of the {@link #mSystemMediaRoutes}. */
public Stream<MediaRoute2Info> getRoutesStream() {
- return mSystemMediaRoutes.stream();
+ return mSystemMediaRoutes.values().stream();
+ }
+
+ @Nullable
+ public MediaRoute2Info getRouteByOriginalId(String routeOriginalId) {
+ return mSystemMediaRoutes.get(routeOriginalId);
+ }
+
+ /**
+ * Requests the creation of a system media routing session.
+ *
+ * @param requestId The request id.
+ * @param uid The uid of the package whose media to route, or {@link Process#INVALID_UID} if
+ * not applicable.
+ * @param packageName The name of the package whose media to route.
+ * @param originalRouteId The {@link MediaRoute2Info#getOriginalId() original route id} of
+ * the route that should be initially selected.
+ * @param callback A {@link MediaRoute2ProviderServiceProxy.SystemMediaSessionCallback} for
+ * events.
+ * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
+ */
+ public void requestCreateSystemMediaSession(
+ long requestId,
+ int uid,
+ String packageName,
+ String originalRouteId,
+ SystemMediaSessionCallback callback) {
+ var targetRouteId = mNewOriginalIdToSourceOriginalIdMap.get(originalRouteId);
+ if (targetRouteId == null) {
+ Log.w(
+ TAG,
+ "Failed system media session creation due to lack of mapping for id: "
+ + originalRouteId);
+ callback.onRequestFailed(
+ requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ } else {
+ mProxy.requestCreateSystemMediaSession(
+ requestId,
+ uid,
+ packageName,
+ targetRouteId,
+ /* sessionHints= */ null,
+ callback);
+ }
+ }
+
+ public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) {
+ // TODO: Map the target route to the source route original id.
+ throw new UnsupportedOperationException("TODO Implement");
+ }
+
+ public void releaseSession(long requestId, String originalSessionId) {
+ mProxy.releaseSession(requestId, originalSessionId);
}
/**
@@ -165,22 +448,177 @@ import java.util.stream.Stream;
if (providerInfo == null) {
return null;
}
- ArraySet<MediaRoute2Info> routes = new ArraySet<>();
- providerInfo.getRoutes().stream()
- .filter(MediaRoute2Info::supportsSystemMediaRouting)
- .forEach(
- route -> {
- String id =
- ROUTE_ID_PREFIX_SYSTEM
- + route.getProviderId()
- + ROUTE_ID_SYSTEM_SEPARATOR
- + route.getOriginalId();
- routes.add(
- new MediaRoute2Info.Builder(id, route.getName())
- .addFeature(FEATURE_LIVE_AUDIO)
- .build());
- });
- return new ProviderProxyRecord(serviceProxy, Collections.unmodifiableSet(routes));
+ Map<String, MediaRoute2Info> routesMap = new ArrayMap<>();
+ Map<String, String> idMap = new ArrayMap<>();
+ for (MediaRoute2Info sourceRoute : providerInfo.getRoutes()) {
+ if (!sourceRoute.supportsSystemMediaRouting()) {
+ continue;
+ }
+ String id =
+ asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId());
+ var newRoute =
+ new MediaRoute2Info.Builder(id, sourceRoute.getName())
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .build();
+ routesMap.put(id, newRoute);
+ idMap.put(id, sourceRoute.getOriginalId());
+ }
+ return new ProviderProxyRecord(
+ serviceProxy,
+ Collections.unmodifiableMap(routesMap),
+ Collections.unmodifiableMap(idMap));
+ }
+ }
+
+ private class SystemMediaSessionCallbackImpl implements SystemMediaSessionCallback {
+
+ private final String mProviderId;
+ private final long mRequestId;
+ private final String mClientPackageName;
+ // Accessed only on mHandler.
+ @Nullable private SystemMediaSessionRecord mSessionRecord;
+
+ private SystemMediaSessionCallbackImpl(
+ String providerId, long requestId, String clientPackageName) {
+ mProviderId = providerId;
+ mRequestId = requestId;
+ mClientPackageName = clientPackageName;
+ }
+
+ @Override
+ public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
+ mHandler.post(
+ () -> {
+ if (mSessionRecord != null) {
+ mSessionRecord.onSessionUpdate(sessionInfo);
+ }
+ SystemMediaSessionRecord systemMediaSessionRecord =
+ new SystemMediaSessionRecord(mProviderId, sessionInfo);
+ RoutingSessionInfo translatedSession;
+ synchronized (mLock) {
+ mSessionRecord = systemMediaSessionRecord;
+ mPackageNameToSessionRecord.put(
+ mClientPackageName, systemMediaSessionRecord);
+ mPendingSessionCreations.remove(mRequestId);
+ translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
+ }
+ onSessionOverrideUpdated(translatedSession);
+ });
+ }
+
+ @Override
+ public void onRequestFailed(long requestId, @Reason int reason) {
+ mHandler.post(
+ () -> {
+ if (mSessionRecord != null) {
+ mSessionRecord.onRequestFailed(requestId, reason);
+ }
+ synchronized (mLock) {
+ mPendingSessionCreations.remove(mRequestId);
+ }
+ notifyRequestFailed(requestId, reason);
+ });
+ }
+
+ @Override
+ public void onSessionReleased() {
+ mHandler.post(
+ () -> {
+ if (mSessionRecord != null) {
+ mSessionRecord.onSessionReleased();
+ } else {
+ // Should never happen. The session hasn't yet been created.
+ throw new IllegalStateException();
+ }
+ });
+ }
+ }
+
+ private class SystemMediaSessionRecord implements SystemMediaSessionCallback {
+
+ private final String mProviderId;
+
+ @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ @NonNull
+ private RoutingSessionInfo mSourceSessionInfo;
+
+ /**
+ * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system
+ * provider ids}.
+ */
+ @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ @NonNull
+ private RoutingSessionInfo mTranslatedSessionInfo;
+
+ SystemMediaSessionRecord(
+ @NonNull String providerId, @NonNull RoutingSessionInfo sessionInfo) {
+ mProviderId = providerId;
+ mSourceSessionInfo = sessionInfo;
+ mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+ }
+
+ @Override
+ public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
+ RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo;
+ synchronized (mLock) {
+ mSourceSessionInfo = sessionInfo;
+ mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+ }
+ onSessionOverrideUpdated(translatedSessionInfo);
+ }
+
+ @Override
+ public void onRequestFailed(long requestId, @Reason int reason) {
+ notifyRequestFailed(requestId, reason);
+ }
+
+ @Override
+ public void onSessionReleased() {
+ synchronized (mLock) {
+ removeSelfFromSessionMap();
+ }
+ notifyGlobalSessionInfoUpdated();
+ }
+
+ @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ @Nullable
+ public ProviderProxyRecord getProxyRecord() {
+ ProviderProxyRecord provider = mProxyRecords.get(mProviderId);
+ if (provider == null) {
+ // Unexpected condition where the proxy is no longer available while there's an
+ // ongoing session. Could happen due to a crash in the provider process.
+ removeSelfFromSessionMap();
+ }
+ return provider;
+ }
+
+ @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ private void removeSelfFromSessionMap() {
+ mPackageNameToSessionRecord.remove(mSourceSessionInfo.getClientPackageName());
+ }
+
+ private RoutingSessionInfo asSystemProviderSession(RoutingSessionInfo session) {
+ var builder =
+ new RoutingSessionInfo.Builder(session)
+ .setProviderId(mUniqueId)
+ .setSystemSession(true)
+ .clearSelectedRoutes()
+ .clearSelectableRoutes()
+ .clearDeselectableRoutes()
+ .clearTransferableRoutes();
+ session.getSelectedRoutes().stream()
+ .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+ .forEach(builder::addSelectedRoute);
+ session.getSelectableRoutes().stream()
+ .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+ .forEach(builder::addSelectableRoute);
+ session.getDeselectableRoutes().stream()
+ .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+ .forEach(builder::addDeselectableRoute);
+ session.getTransferableRoutes().stream()
+ .map(it -> uniqueIdAsSystemRouteId(session.getProviderId(), it))
+ .forEach(builder::addTransferableRoute);
+ return builder.build();
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 89902f7f8321..7cbbe2938fd5 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -101,4 +101,10 @@ public interface NotificationDelegate {
void onNotificationFeedbackReceived(String key, Bundle feedback);
void prepareForPossibleShutdown();
+
+ /**
+ * Called when the notification should be unbundled.
+ * @param key the notification key
+ */
+ void unbundleNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c6d7fc7508da..7375a68c547b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -112,6 +112,7 @@ import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationClassification;
import static android.service.notification.Flags.notificationForceGrouping;
+import static android.service.notification.Flags.notificationRegroupOnClassification;
import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -1851,6 +1852,42 @@ public class NotificationManagerService extends SystemService {
}
}
+ @Override
+ public void unbundleNotification(String key) {
+ if (!(notificationClassification() && notificationRegroupOnClassification())) {
+ return;
+ }
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) {
+ return;
+ }
+
+ if (DBG) {
+ Slog.v(TAG, "unbundleNotification: " + r);
+ }
+
+ boolean hasOriginalSummary = false;
+ if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
+ final String oldGroupKey = GroupHelper.getFullAggregateGroupKey(
+ r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId());
+ NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
+ // We only care about app-provided valid groups
+ hasOriginalSummary = (groupSummary != null
+ && !GroupHelper.isAggregatedGroup(groupSummary));
+ }
+
+ // Only NotificationRecord's mChannel is updated when bundled, the Notification
+ // mChannelId will always be the original channel.
+ String origChannelId = r.getNotification().getChannelId();
+ NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
+ r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
+ if (originalChannel != null && !origChannelId.equals(r.getChannel().getId())) {
+ r.updateNotificationChannel(originalChannel);
+ mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary);
+ }
+ }
+ }
};
NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 749952e3336f..15377d6b269a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -559,7 +559,7 @@ public class PreferencesHelper implements RankingConfig {
if (r.uid == UNKNOWN_UID) {
if (Flags.persistIncompleteRestoreData()) {
- r.userId = userId;
+ r.userIdWhenUidUnknown = userId;
}
mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
} else {
@@ -756,7 +756,7 @@ public class PreferencesHelper implements RankingConfig {
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
- out.attributeInt(null, ATT_USERID, r.userId);
+ out.attributeInt(null, ATT_USERID, r.userIdWhenUidUnknown);
}
if (!forBackup) {
@@ -1959,7 +1959,7 @@ public class PreferencesHelper implements RankingConfig {
ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
synchronized (mLock) {
for (PackagePreferences p : mPackagePreferences.values()) {
- if (p.userId != userId) {
+ if (UserHandle.getUserId(p.uid) != userId) {
continue;
}
int totalChannelCount = p.channels.size();
@@ -3189,7 +3189,7 @@ public class PreferencesHelper implements RankingConfig {
// Until we enable the UI, we should return false.
boolean canHavePromotedNotifs = android.app.Flags.uiRichOngoing();
- @UserIdInt int userId;
+ @UserIdInt int userIdWhenUidUnknown;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 3528d3d96c2b..8a35006e0f6a 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -487,6 +487,20 @@ public interface Computer extends PackageDataSnapshot {
ProviderInfo resolveContentProvider(@NonNull String name,
@PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId, int callingUid);
+ /**
+ * Resolves a ContentProvider on behalf of a UID
+ * @param name Authority of the content provider
+ * @param flags option flags to modify the data returned.
+ * @param userId Current user ID
+ * @param filterCallingUid UID of the caller who's access to the content provider
+ * is to be checked
+ * @return
+ */
+ @Nullable
+ ProviderInfo resolveContentProviderForUid(@NonNull String name,
+ @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+ int filterCallingUid);
+
@Nullable
ProviderInfo getGrantImplicitAccessProviderInfo(int recipientUid,
@NonNull String visibleAuthority);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index be2f58dc276c..38617621bf89 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4749,6 +4749,38 @@ public class ComputerEngine implements Computer {
@Nullable
@Override
+ public ProviderInfo resolveContentProviderForUid(@NonNull String name,
+ @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+ int filterCallingUid) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.RESOLVE_COMPONENT_FOR_UID,
+ "resolveContentProviderForUid");
+
+ int callingUid = Binder.getCallingUid();
+ int filterUserId = UserHandle.getUserId(filterCallingUid);
+ enforceCrossUserPermission(callingUid, filterUserId, false, false,
+ "resolveContentProviderForUid");
+
+ // Real callingUid should be able to see filterCallingUid
+ if (filterAppAccess(filterCallingUid, callingUid)) {
+ return null;
+ }
+
+ ProviderInfo pInfo = resolveContentProvider(name, flags, userId, filterCallingUid);
+ if (pInfo == null) {
+ return null;
+ }
+ // Real callingUid should be able to see the ContentProvider accessible to filterCallingUid
+ ProviderInfo pInfo2 = resolveContentProvider(name, flags, userId, callingUid);
+ if (pInfo2 != null
+ && Objects.equals(pInfo.name, pInfo2.name)
+ && Objects.equals(pInfo.authority, pInfo2.authority)) {
+ return pInfo;
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
public ProviderInfo resolveContentProvider(@NonNull String name,
@PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
int callingUid) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index f05c54d666df..b11d3499d391 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -1129,6 +1129,12 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
}
@Override
+ public final ProviderInfo resolveContentProviderForUid(String name,
+ @PackageManager.ResolveInfoFlagsBits long flags, int userId, int filterCallingUid) {
+ return snapshot().resolveContentProviderForUid(name, flags, userId, filterCallingUid);
+ }
+
+ @Override
@Deprecated
public final void resetApplicationPreferences(int userId) {
mPreferredActivityHelper.resetApplicationPreferences(userId);
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index e9cb279439a6..e989d6875d15 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -40,7 +40,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.UndecFunction;
@@ -351,22 +351,22 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Override
public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
- Boolean, SyncNotedAppOp> superImpl) {
+ @Nullable String message, boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
+ Boolean, Integer, SyncNotedAppOp> superImpl) {
if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
}
@Override
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 3f9144f0d980..dea52fd7cd99 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -53,7 +53,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.UndecFunction;
import com.android.server.LocalServices;
@@ -248,11 +248,12 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, int virtualDeviceId,
boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String,
- Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+ boolean shouldCollectMessage, int notedCount,
+ @NonNull NonaFunction<Integer, Integer, String, String,
+ Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) {
return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount);
}
@Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c4d1cc723804..ec0f25169d75 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4068,7 +4068,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Nullable IBinder focusedToken) {
boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
focusedToken);
- if (handled && Arrays.stream(event.getKeycodes()).anyMatch(
+ if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
(keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
mPowerKeyHandled = true;
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 0c3c46c75eee..7f88e7463208 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -479,6 +479,7 @@ public class Notifier {
case PowerManager.PARTIAL_WAKE_LOCK:
return BatteryStats.WAKE_TYPE_PARTIAL;
+ case PowerManager.FULL_WAKE_LOCK:
case PowerManager.SCREEN_DIM_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
return BatteryStats.WAKE_TYPE_FULL;
@@ -503,6 +504,31 @@ public class Notifier {
}
}
+ @VisibleForTesting
+ int getWakelockMonitorTypeForLogging(int flags) {
+ switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+ case PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK,
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+ return PowerManager.FULL_WAKE_LOCK;
+ case PowerManager.DRAW_WAKE_LOCK:
+ return PowerManager.DRAW_WAKE_LOCK;
+ case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+ if (mSuspendWhenScreenOffDueToProximityConfig) {
+ return -1;
+ }
+ return PowerManager.PARTIAL_WAKE_LOCK;
+ case PowerManager.PARTIAL_WAKE_LOCK:
+ return PowerManager.PARTIAL_WAKE_LOCK;
+ case PowerManager.DOZE_WAKE_LOCK:
+ // Doze wake locks are an internal implementation detail of the
+ // communication between dream manager service and power manager
+ // service. They have no additive battery impact.
+ return -1;
+ default:
+ return -1;
+ }
+ }
+
/**
* Notifies that the device is changing wakefulness.
* This function may be called even if the previous change hasn't finished in
@@ -1288,7 +1314,7 @@ public class Notifier {
if (mBatteryStatsInternal == null) {
return;
}
- final int type = flags & PowerManager.WAKE_LOCK_LEVEL_MASK;
+ final int type = getWakelockMonitorTypeForLogging(flags);
if (workSource == null || workSource.isEmpty()) {
final int mappedUid = mBatteryStatsInternal.getOwnerUid(ownerUid);
mFrameworkStatsLogger.wakelockStateChanged(mappedUid, tag, type, eventType);
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 83461125b404..a0bc77e939d1 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -24,6 +24,7 @@ import static com.android.server.power.hint.Flags.cpuHeadroomAffinityCheck;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
import static com.android.server.power.hint.Flags.resetOnForkEnabled;
+import android.Manifest;
import android.adpf.ISessionManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -59,6 +60,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SessionCreationConfig;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -79,7 +83,10 @@ import com.android.server.SystemService;
import com.android.server.power.hint.HintManagerService.AppHintSession.SessionModes;
import com.android.server.utils.Slogf;
+import java.io.BufferedReader;
import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -95,6 +102,8 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** An hint service implementation that runs in System Server process. */
public final class HintManagerService extends SystemService {
@@ -103,10 +112,10 @@ public final class HintManagerService extends SystemService {
private static final int EVENT_CLEAN_UP_UID = 3;
@VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
- // The minimum interval between the headroom calls as rate limiting.
- private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
- private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
+ // example: cpu 2255 34 2290 22625563 6290 127 456
+ private static final Pattern PROC_STAT_CPU_TIME_TOTAL_PATTERN =
+ Pattern.compile("cpu\\s+(?<user>[0-9]+)\\s(?<nice>[0-9]+).+");
@VisibleForTesting final long mHintSessionPreferredRate;
@@ -192,10 +201,26 @@ public final class HintManagerService extends SystemService {
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
- private static final String PROPERTY_CHECK_HEADROOM_AFFINITY = "persist.hms.check_affinity";
+ private static final String PROPERTY_CHECK_HEADROOM_AFFINITY =
+ "persist.hms.check_headroom_affinity";
+ private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS =
+ "persist.hms.check_headroom_proc_stat_min_millis";
private Boolean mFMQUsesIntegratedEventFlag = false;
private final Object mCpuHeadroomLock = new Object();
+ @VisibleForTesting
+ final float mJiffyMillis;
+ private final int mCheckHeadroomProcStatMinMillis;
+ @GuardedBy("mCpuHeadroomLock")
+ private long mLastCpuUserModeTimeCheckedMillis = 0;
+ @GuardedBy("mCpuHeadroomLock")
+ private long mLastCpuUserModeJiffies = 0;
+ @GuardedBy("mCpuHeadroomLock")
+ private final Map<Integer, Long> mUidToLastUserModeJiffies;
+ @VisibleForTesting
+ private String mProcStatFilePathOverride = null;
+ @VisibleForTesting
+ private boolean mEnforceCpuHeadroomUserModeCpuTimeCheck = false;
private ISessionManager mSessionManager;
@@ -310,8 +335,16 @@ public final class HintManagerService extends SystemService {
new GpuHeadroomParamsInternal().calculationWindowMillis;
if (mSupportInfo.headroom.isCpuSupported) {
mCpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.cpuMinIntervalMillis);
+ mUidToLastUserModeJiffies = new ArrayMap<>();
+ long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000.0f / jiffyHz;
+ mCheckHeadroomProcStatMinMillis = SystemProperties.getInt(
+ PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, 50);
} else {
mCpuHeadroomCache = null;
+ mUidToLastUserModeJiffies = null;
+ mJiffyMillis = 0.0f;
+ mCheckHeadroomProcStatMinMillis = 0;
}
if (mSupportInfo.headroom.isGpuSupported) {
mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis);
@@ -370,6 +403,12 @@ public final class HintManagerService extends SystemService {
return supportInfo;
}
+ @VisibleForTesting
+ void setProcStatPathOverride(String override) {
+ mProcStatFilePathOverride = override;
+ mEnforceCpuHeadroomUserModeCpuTimeCheck = true;
+ }
+
private ServiceThread createCleanUpThread() {
final ServiceThread handlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_LOWEST, true /*allowIo*/);
@@ -851,6 +890,11 @@ public final class HintManagerService extends SystemService {
mChannelMap.remove(uid);
}
}
+ synchronized (mCpuHeadroomLock) {
+ if (mSupportInfo.headroom.isCpuSupported && mUidToLastUserModeJiffies != null) {
+ mUidToLastUserModeJiffies.remove(uid);
+ }
+ }
});
}
@@ -1230,7 +1274,7 @@ public final class HintManagerService extends SystemService {
// Only call into AM if the tid is either isolated or invalid
if (isolatedPids == null) {
// To avoid deadlock, do not call into AMS if the call is from system.
- if (uid == Process.SYSTEM_UID) {
+ if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
return tid;
}
isolatedPids = mAmInternal.getIsolatedProcesses(uid);
@@ -1485,14 +1529,17 @@ public final class HintManagerService extends SystemService {
throw new UnsupportedOperationException();
}
checkCpuHeadroomParams(params);
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final CpuHeadroomParams halParams = new CpuHeadroomParams();
- halParams.tids = new int[]{Binder.getCallingPid()};
+ halParams.tids = new int[]{pid};
halParams.calculationType = params.calculationType;
halParams.calculationWindowMillis = params.calculationWindowMillis;
if (params.usesDeviceHeadroom) {
halParams.tids = new int[]{};
} else if (params.tids != null && params.tids.length > 0) {
- if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+ if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && SystemProperties.getBoolean(
+ PROPERTY_CHECK_HEADROOM_TID, true)) {
final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
for (int tid : params.tids) {
if (Process.getThreadGroupLeader(tid) != tgid) {
@@ -1515,6 +1562,20 @@ public final class HintManagerService extends SystemService {
if (res != null) return res;
}
}
+ final boolean shouldCheckUserModeCpuTime =
+ mEnforceCpuHeadroomUserModeCpuTimeCheck
+ || (UserHandle.getAppId(uid) != Process.SYSTEM_UID
+ && mContext.checkCallingPermission(
+ Manifest.permission.DEVICE_POWER)
+ == PackageManager.PERMISSION_DENIED);
+
+ if (shouldCheckUserModeCpuTime) {
+ synchronized (mCpuHeadroomLock) {
+ if (!checkPerUidUserModeCpuTimeElapsedLocked(uid)) {
+ return null;
+ }
+ }
+ }
// return from HAL directly
try {
final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
@@ -1528,6 +1589,11 @@ public final class HintManagerService extends SystemService {
mCpuHeadroomCache.add(halParams, result);
}
}
+ if (shouldCheckUserModeCpuTime) {
+ synchronized (mCpuHeadroomLock) {
+ mUidToLastUserModeJiffies.put(uid, mLastCpuUserModeJiffies);
+ }
+ }
return result;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
@@ -1556,6 +1622,40 @@ public final class HintManagerService extends SystemService {
}
}
+ // check if there has been sufficient user mode cpu time elapsed since last call
+ // from the same uid
+ @GuardedBy("mCpuHeadroomLock")
+ private boolean checkPerUidUserModeCpuTimeElapsedLocked(int uid) {
+ // skip checking proc stat if it's within mCheckHeadroomProcStatMinMillis
+ if (System.currentTimeMillis() - mLastCpuUserModeTimeCheckedMillis
+ > mCheckHeadroomProcStatMinMillis) {
+ try {
+ mLastCpuUserModeJiffies = getUserModeJiffies();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get user mode CPU time", e);
+ return false;
+ }
+ mLastCpuUserModeTimeCheckedMillis = System.currentTimeMillis();
+ }
+ if (mUidToLastUserModeJiffies.containsKey(uid)) {
+ long uidLastUserModeJiffies = mUidToLastUserModeJiffies.get(uid);
+ if ((mLastCpuUserModeJiffies - uidLastUserModeJiffies) * mJiffyMillis
+ < mSupportInfo.headroom.cpuMinIntervalMillis) {
+ Slog.w(TAG, "UID " + uid + " is requesting CPU headroom too soon");
+ Slog.d(TAG, "UID " + uid + " last request at "
+ + uidLastUserModeJiffies * mJiffyMillis
+ + "ms with device currently at "
+ + mLastCpuUserModeJiffies * mJiffyMillis
+ + "ms, the interval: "
+ + (mLastCpuUserModeJiffies - uidLastUserModeJiffies)
+ * mJiffyMillis + "ms is less than require minimum interval "
+ + mSupportInfo.headroom.cpuMinIntervalMillis + "ms");
+ return false;
+ }
+ }
+ return true;
+ }
+
private void checkCpuHeadroomParams(CpuHeadroomParamsInternal params) {
boolean calculationTypeMatched = false;
try {
@@ -1731,6 +1831,27 @@ public final class HintManagerService extends SystemService {
}
}
+ private long getUserModeJiffies() throws IOException {
+ String filePath =
+ mProcStatFilePathOverride == null ? "/proc/stat" : mProcStatFilePathOverride;
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher matcher = PROC_STAT_CPU_TIME_TOTAL_PATTERN.matcher(line.trim());
+ if (matcher.find()) {
+ long userJiffies = Long.parseLong(matcher.group("user"));
+ long niceJiffies = Long.parseLong(matcher.group("nice"));
+ Slog.d(TAG,
+ "user: " + userJiffies + " nice: " + niceJiffies
+ + " total " + (userJiffies + niceJiffies));
+ reader.close();
+ return userJiffies + niceJiffies;
+ }
+ }
+ }
+ throw new IllegalStateException("Can't find cpu line in " + filePath);
+ }
+
private boolean checkGraphicsPipelineValid(SessionCreationConfig creationConfig, int uid) {
if (creationConfig.modesToEnable == null) {
return true;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index f8877ad912b5..c18918fd9f8b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -2175,6 +2175,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
+ /**
+ * Called when the notification should be unbundled.
+ * @param key the notification key
+ */
+ @Override
+ public void unbundleNotification(@Nullable String key) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+ Binder.withCleanCallingIdentity(() -> {
+ mNotificationDelegate.unbundleNotification(key);
+ });
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index dad3a784cfb9..bb163ef4c1d5 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -369,10 +369,9 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener {
tagName = parser.getName();
if (TAG_QUOTA.equals(tagName)) {
CacheQuotaHint request = getRequestFromXml(parser);
- if (request == null) {
- continue;
+ if (request != null) {
+ quotas.add(request);
}
- quotas.add(request);
}
}
eventType = parser.next();
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 19eba5fe5755..90c2216f6b22 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -51,8 +51,11 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
/**
* Base class for a Snapshot controller
@@ -148,43 +151,60 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
protected abstract Rect getLetterboxInsets(ActivityRecord topActivity);
/**
- * This is different than {@link #recordSnapshotInner(TYPE)} because it doesn't store
- * the snapshot to the cache and returns the TaskSnapshot immediately.
- *
- * This is only used for testing so the snapshot content can be verified.
+ * This is different than {@link #recordSnapshotInner(TYPE, boolean, Consumer)} because it
+ * doesn't store the snapshot to the cache and returns the TaskSnapshot immediately.
*/
@VisibleForTesting
- TaskSnapshot captureSnapshot(TYPE source) {
- final TaskSnapshot snapshot;
+ SnapshotSupplier captureSnapshot(TYPE source, boolean allowAppTheme) {
+ final SnapshotSupplier supplier = new SnapshotSupplier();
switch (getSnapshotMode(source)) {
- case SNAPSHOT_MODE_NONE:
- return null;
case SNAPSHOT_MODE_APP_THEME:
- snapshot = drawAppThemeSnapshot(source);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot");
+ if (Flags.excludeDrawingAppThemeSnapshotFromLock()) {
+ if (allowAppTheme) {
+ supplier.setSupplier(drawAppThemeSnapshot(source));
+ }
+ } else {
+ final Supplier<TaskSnapshot> original = drawAppThemeSnapshot(source);
+ final TaskSnapshot snapshot = original != null ? original.get() : null;
+ supplier.setSnapshot(snapshot);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
break;
case SNAPSHOT_MODE_REAL:
- snapshot = snapshot(source);
+ supplier.setSnapshot(snapshot(source));
break;
default:
- snapshot = null;
break;
}
- return snapshot;
+ return supplier;
}
- final TaskSnapshot recordSnapshotInner(TYPE source) {
+ /**
+ * @param allowAppTheme If true, allows to draw app theme snapshot when it's not allowed to take
+ * a real screenshot, but create a fake representation of the app.
+ * @param inLockConsumer Extra task to do in WM lock when first get the snapshot object.
+ */
+ final SnapshotSupplier recordSnapshotInner(TYPE source, boolean allowAppTheme,
+ @Nullable Consumer<TaskSnapshot> inLockConsumer) {
if (shouldDisableSnapshots()) {
return null;
}
- final TaskSnapshot snapshot = captureSnapshot(source);
- if (snapshot == null) {
- return null;
- }
- mCache.putSnapshot(source, snapshot);
- return snapshot;
+ final SnapshotSupplier supplier = captureSnapshot(source, allowAppTheme);
+ supplier.setConsumer(t -> {
+ synchronized (mService.mGlobalLock) {
+ if (!source.isAttached()) {
+ return;
+ }
+ mCache.putSnapshot(source, t);
+ if (inLockConsumer != null) {
+ inLockConsumer.accept(t);
+ }
+ }
+ });
+ return supplier;
}
- @VisibleForTesting
int getSnapshotMode(TYPE source) {
final int type = source.getActivityType();
if (type == ACTIVITY_TYPE_RECENTS || type == ACTIVITY_TYPE_DREAM) {
@@ -400,7 +420,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
* If we are not allowed to take a real screenshot, this attempts to represent the app as best
* as possible by using the theme's window background.
*/
- private TaskSnapshot drawAppThemeSnapshot(TYPE source) {
+ private Supplier<TaskSnapshot> drawAppThemeSnapshot(TYPE source) {
final ActivityRecord topActivity = getTopActivity(source);
if (topActivity == null) {
return null;
@@ -432,26 +452,46 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
decorPainter.setInsets(systemBarInsets);
decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawFrame */);
node.end(c);
- final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
- if (hwBitmap == null) {
- return null;
- }
+
final Rect contentInsets = new Rect(systemBarInsets);
final Rect letterboxInsets = getLetterboxInsets(topActivity);
InsetUtils.addInsets(contentInsets, letterboxInsets);
- // Note, the app theme snapshot is never translucent because we enforce a non-translucent
- // color above
- final TaskSnapshot taskSnapshot = new TaskSnapshot(
- System.currentTimeMillis() /* id */,
- SystemClock.elapsedRealtimeNanos() /* captureTime */,
- topActivity.mActivityComponent, hwBitmap.getHardwareBuffer(),
- hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
- mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
- contentInsets, letterboxInsets, false /* isLowResolution */,
- false /* isRealSnapshot */, source.getWindowingMode(),
- attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */,
- topActivity.getConfiguration().uiMode /* uiMode */);
- return validateSnapshot(taskSnapshot);
+
+ final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+ builder.setIsRealSnapshot(false);
+ builder.setId(System.currentTimeMillis());
+ builder.setContentInsets(contentInsets);
+ builder.setLetterboxInsets(letterboxInsets);
+
+ builder.setTopActivityComponent(topActivity.mActivityComponent);
+ // Note, the app theme snapshot is never translucent because we enforce a
+ // non-translucent color above.
+ builder.setIsTranslucent(false);
+ builder.setWindowingMode(source.getWindowingMode());
+ builder.setAppearance(attrs.insetsFlags.appearance);
+ builder.setUiMode(topActivity.getConfiguration().uiMode);
+
+ builder.setRotation(mainWindow.getWindowConfiguration().getRotation());
+ builder.setOrientation(mainWindow.getConfiguration().orientation);
+ builder.setTaskSize(new Point(taskWidth, taskHeight));
+ builder.setCaptureTime(SystemClock.elapsedRealtimeNanos());
+
+ return () -> {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "drawAppThemeSnapshot_acquire");
+ // Do not hold WM lock when calling to render thread.
+ final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width,
+ height);
+ if (hwBitmap == null) {
+ return null;
+ }
+ builder.setSnapshot(hwBitmap.getHardwareBuffer());
+ builder.setColorSpace(hwBitmap.getColorSpace());
+ return validateSnapshot(builder.build());
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ };
}
static Rect getSystemBarInsets(Rect frame, InsetsState state) {
@@ -482,4 +522,45 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
pw.println(prefix + "mSnapshotEnabled=" + mSnapshotEnabled);
mCache.dump(pw, prefix);
}
+
+ static class SnapshotSupplier implements Supplier<TaskSnapshot> {
+
+ private TaskSnapshot mSnapshot;
+ private boolean mHasSet;
+ private Consumer<TaskSnapshot> mConsumer;
+ private Supplier<TaskSnapshot> mSupplier;
+
+ /** Callback when the snapshot is get for the first time. */
+ void setConsumer(@NonNull Consumer<TaskSnapshot> consumer) {
+ mConsumer = consumer;
+ }
+
+ void setSupplier(@NonNull Supplier<TaskSnapshot> createSupplier) {
+ mSupplier = createSupplier;
+ }
+
+ void setSnapshot(TaskSnapshot snapshot) {
+ mSnapshot = snapshot;
+ }
+
+ void handleSnapshot() {
+ if (mHasSet) {
+ return;
+ }
+ mHasSet = true;
+ if (mSnapshot == null) {
+ mSnapshot = mSupplier != null ? mSupplier.get() : null;
+ }
+ if (mConsumer != null && mSnapshot != null) {
+ mConsumer.accept(mSnapshot);
+ }
+ }
+
+ @Override
+ @Nullable
+ public TaskSnapshot get() {
+ handleSnapshot();
+ return mSnapshot;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83b273c04648..745d41dd0d3d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3898,11 +3898,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final TaskFragment taskFragment = getTaskFragment();
if (next != null && taskFragment != null && taskFragment.isEmbedded()) {
final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
- final TaskFragment adjacent =
- organized != null ? organized.getAdjacentTaskFragment() : null;
- if (adjacent != null && next.isDescendantOf(adjacent)
- && organized.topRunningActivity() == null) {
- delayRemoval = organized.isDelayLastActivityRemoval();
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ delayRemoval = organized != null
+ && organized.topRunningActivity() == null
+ && organized.isDelayLastActivityRemoval()
+ && organized.forOtherAdjacentTaskFragments(next::isDescendantOf);
+ } else {
+ final TaskFragment adjacent =
+ organized != null ? organized.getAdjacentTaskFragment() : null;
+ if (adjacent != null && next.isDescendantOf(adjacent)
+ && organized.topRunningActivity() == null) {
+ delayRemoval = organized.isDelayLastActivityRemoval();
+ }
}
}
@@ -4880,15 +4887,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* @see #canShowWhenLocked(ActivityRecord)
*/
boolean canShowWhenLocked() {
+ if (!canShowWhenLocked(this)) {
+ return false;
+ }
final TaskFragment taskFragment = getTaskFragment();
- if (taskFragment != null && taskFragment.getAdjacentTaskFragment() != null
- && taskFragment.isEmbedded()) {
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- final ActivityRecord r = adjacentTaskFragment.getTopNonFinishingActivity();
- return canShowWhenLocked(this) && canShowWhenLocked(r);
- } else {
- return canShowWhenLocked(this);
+ if (taskFragment == null || !taskFragment.hasAdjacentTaskFragment()
+ || !taskFragment.isEmbedded()) {
+ // No embedded adjacent that need to be checked.
+ return true;
+ }
+
+ // Make sure the embedded adjacent can also be shown.
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment()
+ .getTopNonFinishingActivity();
+ return canShowWhenLocked(adjacentActivity);
}
+ final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments(
+ adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity()));
+ return !hasAdjacentNotAllowToShow;
}
/**
@@ -8528,8 +8545,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (Flags.immersiveAppRepositioning()
- && !mAppCompatController.getAppCompatAspectRatioPolicy()
+ if (!mAppCompatController.getAppCompatAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio()
&& !mAppCompatController.getAppCompatAspectRatioOverrides()
.hasFullscreenOverride()) {
@@ -8551,18 +8567,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
}
}
- // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
- // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
- // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
- // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- if (!Flags.immersiveAppRepositioning()
- && !mAppCompatController.getAppCompatAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()
- && !scmPolicy.isInSizeCompatModeForBounds()
- && !mAppCompatController.getAppCompatAspectRatioOverrides()
- .hasFullscreenOverride()) {
- resolveAspectRatioRestriction(newParentConfiguration);
- }
if (isFixedOrientationLetterboxAllowed
|| scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
@@ -8819,9 +8823,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean isImmersiveMode(@NonNull Rect parentBounds) {
- if (!Flags.immersiveAppRepositioning()) {
- return false;
- }
if (!mResolveConfigHint.mUseOverrideInsetsForConfig
&& mWmService.mFlags.mInsetsDecoupledConfiguration) {
return false;
@@ -10355,7 +10356,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
if (!isVisibleRequested()) {
// TODO(b/294925498): Remove this finishing check once we have accurate ready tracking.
- if (task != null && task.getPausingActivity() == this) {
+ if (task != null && task.getPausingActivity() == this
+ // Display is asleep, so nothing will be visible anyways.
+ && !mDisplayContent.isSleeping()) {
// Visibility of starting activities isn't calculated until pause-complete, so if
// this is not paused yet, don't consider it ready.
return false;
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 9aaa0e1cfd6b..cfd324830db5 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -38,6 +38,7 @@ import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.Supplier;
/**
* When an app token becomes invisible, we take a snapshot (bitmap) and put it into our cache.
@@ -355,7 +356,9 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
final int[] mixedCode = new int[size];
if (size == 1) {
final ActivityRecord singleActivity = activity.get(0);
- final TaskSnapshot snapshot = recordSnapshotInner(singleActivity);
+ final Supplier<TaskSnapshot> supplier = recordSnapshotInner(singleActivity,
+ false /* allowAppTheme */, null /* inLockConsumer */);
+ final TaskSnapshot snapshot = supplier != null ? supplier.get() : null;
if (snapshot != null) {
mixedCode[0] = getSystemHashCode(singleActivity);
addUserSavedFile(singleActivity.mUserId, snapshot, mixedCode);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5eee8ece6a67..290f155bb4cd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -314,6 +314,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Supplier;
/**
* System service for managing activities and their containers (task, displays,... ).
@@ -4038,6 +4039,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "takeTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
+ final Supplier<TaskSnapshot> supplier;
synchronized (mGlobalLock) {
final Task task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
@@ -4050,11 +4052,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// be retrieved by recents. While if updateCache is false, the real snapshot will
// always be taken and the snapshot won't be put into SnapshotPersister.
if (updateCache) {
- return mWindowManager.mTaskSnapshotController.recordSnapshot(task);
+ supplier = mWindowManager.mTaskSnapshotController
+ .getRecordSnapshotSupplier(task);
} else {
return mWindowManager.mTaskSnapshotController.snapshot(task);
}
}
+ return supplier != null ? supplier.get() : null;
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6403,6 +6407,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public boolean shuttingDown(boolean booted, int timeout) {
mShuttingDown = true;
+ mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
synchronized (mGlobalLock) {
mRootWindowContainer.prepareForShutdown();
updateEventDispatchingLocked(booted);
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index e8eae4f96a04..6a0de98c0ffa 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
@@ -36,6 +37,8 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import com.android.window.flags.Flags;
+
/**
* Encapsulate app compat policy logic related to aspect ratio.
*/
@@ -239,7 +242,14 @@ class AppCompatAspectRatioPolicy {
|| AppCompatUtils.isInVrUiMode(mActivityRecord.getConfiguration())
// TODO(b/232898850): Always respect aspect ratio requests.
// Don't set aspect ratio for activity in ActivityEmbedding split.
- || (organizedTf != null && !organizedTf.fillsParent())) {
+ || (organizedTf != null && !organizedTf.fillsParent())
+ // Don't set aspect ratio for resizeable activities in freeform.
+ // {@link ActivityRecord#shouldCreateAppCompatDisplayInsets()} will be false for
+ // both activities that are naturally resizeable and activities that have been
+ // forced resizeable.
+ || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities()
+ && task.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && !mActivityRecord.shouldCreateAppCompatDisplayInsets())) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
index caff96ba4a9f..4fac81b06680 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -35,8 +35,6 @@ import android.annotation.NonNull;
import android.content.res.Configuration;
import android.graphics.Rect;
-import com.android.window.flags.Flags;
-
/**
* Encapsulate overrides and configurations about app compat reachability.
*/
@@ -157,33 +155,27 @@ class AppCompatReachabilityOverrides {
}
/**
- * @return {@value true} if the vertical reachability should be allowed in case of
+ * @return {@code true} if the vertical reachability should be allowed in case of
* thin letterboxing.
*/
boolean allowVerticalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
// When the flag is enabled we allow vertical reachability only if the
// app is not thin letterboxed vertically.
return !isVerticalThinLetterboxed();
}
/**
- * @return {@value true} if the horizontal reachability should be enabled in case of
+ * @return {@code true} if the horizontal reachability should be enabled in case of
* thin letterboxing.
*/
boolean allowHorizontalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
// When the flag is enabled we allow horizontal reachability only if the
// app is not thin pillarboxed.
return !isHorizontalThinLetterboxed();
}
/**
- * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
+ * @return {@code true} if the resulting app is letterboxed in a way defined as thin.
*/
boolean isVerticalThinLetterboxed() {
final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
@@ -200,7 +192,7 @@ class AppCompatReachabilityOverrides {
}
/**
- * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
+ * @return {@code true} if the resulting app is pillarboxed in a way defined as thin.
*/
boolean isHorizontalThinLetterboxed() {
final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index f3b043bb51dd..d278dc3d1be7 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -28,8 +28,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
-import com.android.window.flags.Flags;
-
import java.io.PrintWriter;
import java.util.function.DoubleSupplier;
@@ -202,9 +200,7 @@ class AppCompatSizeCompatModePolicy {
// saved here before resolved bounds are overridden below.
final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
.getAppCompatAspectRatioPolicy();
- final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
- ? aspectRatioPolicy.isAspectRatioApplied()
- : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio();
+ final boolean useResolvedBounds = aspectRatioPolicy.isAspectRatioApplied();
final Rect containerBounds = useResolvedBounds
? new Rect(resolvedBounds)
: newParentConfiguration.windowConfiguration.getBounds();
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 0369a0ff4c76..9f88bc952351 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -164,15 +164,13 @@ final class AppCompatUtils {
appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
+ appCompatTaskInfo.topActivityAppBounds.set(getAppBounds(top));
final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
if (isTopActivityLetterboxed) {
final Rect bounds = top.getBounds();
- final Rect appBounds = getAppBounds(top);
appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
- appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
- appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
// TODO(b/379824541) Remove duplicate information.
appCompatTaskInfo.topActivityLetterboxBounds = bounds;
// We need to consider if letterboxed or pillarboxed.
@@ -281,8 +279,7 @@ final class AppCompatUtils {
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
- info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
- info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityAppBounds.setEmpty();
info.topActivityLetterboxBounds = null;
info.cameraCompatTaskInfo.freeformCameraCompatMode =
CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index dd1af0a497ca..6b6f0111305c 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -29,9 +29,6 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-
-import com.android.internal.R;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -687,11 +684,12 @@ class AsyncRotationController extends FadeAnimationController implements Consume
@Override
public Animation getFadeInAnimation() {
+ final Animation anim = super.getFadeInAnimation();
if (mHasScreenRotationAnimation) {
// Use a shorter animation so it is easier to align with screen rotation animation.
- return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
+ anim.setDuration(getScaledDuration(SHORT_DURATION_MS));
}
- return super.getFadeInAnimation();
+ return anim;
}
@Override
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1a7c6b70f007..fc0df645a2db 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -111,9 +111,7 @@ class BackNavigationController {
}
void onEmbeddedWindowGestureTransferred(@NonNull WindowState host) {
- if (Flags.disallowAppProgressEmbeddedWindow()) {
- mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
- }
+ mNavigationMonitor.onEmbeddedWindowGestureTransferred(host);
}
/**
@@ -215,7 +213,7 @@ class BackNavigationController {
infoBuilder.setFocusedTaskId(currentTask.mTaskId);
}
boolean transferGestureToEmbedded = false;
- if (Flags.disallowAppProgressEmbeddedWindow() && embeddedWindows != null) {
+ if (embeddedWindows != null) {
for (int i = embeddedWindows.size() - 1; i >= 0; --i) {
if (embeddedWindows.get(i).mGestureToEmbedded) {
transferGestureToEmbedded = true;
@@ -997,11 +995,9 @@ class BackNavigationController {
/**
* Handle the pending animation when the running transition finished, all the visibility change
* has applied so ready to start pending predictive back animation.
- * @param targets The final animation targets derived in transition.
* @param finishedTransition The finished transition target.
*/
- void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
- @NonNull Transition finishedTransition) {
+ void onTransitionFinish(@NonNull Transition finishedTransition) {
if (isMonitoringPrepareTransition(finishedTransition)) {
if (mAnimationHandler.mPrepareCloseTransition == null) {
clearBackAnimations(true /* cancel */);
@@ -1049,14 +1045,6 @@ class BackNavigationController {
return;
}
- // Ensure the final animation targets which hidden by transition could be visible.
- for (int i = 0; i < targets.size(); i++) {
- final WindowContainer wc = targets.get(i).mContainer;
- if (wc.mSurfaceControl != null) {
- wc.prepareSurfaces();
- }
- }
-
// The pending builder could be cleared due to prepareSurfaces
// => updateNonSystemOverlayWindowsVisibilityIfNeeded
// => setForceHideNonSystemOverlayWindowIfNeeded
diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java
index 3aa355869d85..00279921953d 100644
--- a/services/core/java/com/android/server/wm/CameraStateMonitor.java
+++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java
@@ -110,8 +110,10 @@ class CameraStateMonitor {
}
void startListeningToCameraState() {
- mCameraManager.registerAvailabilityCallback(
- mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+ if (mCameraManager != null) {
+ mCameraManager.registerAvailabilityCallback(
+ mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+ }
mIsRunning = true;
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 258a87eae196..3c60d8296577 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -289,7 +289,8 @@ class DragDropController {
transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
transaction.show(surfaceControl);
displayContent.reparentToOverlay(transaction, surfaceControl);
- mDragState.updateDragSurfaceLocked(true, touchX, touchY);
+ mDragState.updateDragSurfaceLocked(true /* keepHandling */,
+ displayContent.getDisplayId(), touchX, touchY);
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
}
@@ -483,10 +484,11 @@ class DragDropController {
* Handles motion events.
* @param keepHandling Whether if the drag operation is continuing or this is the last motion
* event.
+ * @param displayId id of the display the X,Y coordinate is n.
* @param newX X coordinate value in dp in the screen coordinate
* @param newY Y coordinate value in dp in the screen coordinate
*/
- void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+ void handleMotionEvent(boolean keepHandling, int displayId, float newX, float newY) {
synchronized (mService.mGlobalLock) {
if (!dragDropActiveLocked()) {
// The drag has ended but the clean-up message has not been processed by
@@ -495,7 +497,7 @@ class DragDropController {
return;
}
- mDragState.updateDragSurfaceLocked(keepHandling, newX, newY);
+ mDragState.updateDragSurfaceLocked(keepHandling, displayId, newX, newY);
}
}
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
index 5372d8b6e796..8f4548fa4fcb 100644
--- a/services/core/java/com/android/server/wm/DragInputEventReceiver.java
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -22,13 +22,13 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.os.Looper;
import android.util.Slog;
import android.view.InputChannel;
-import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.MotionEvent;
@@ -63,6 +63,7 @@ class DragInputEventReceiver extends InputEventReceiver {
return;
}
final MotionEvent motionEvent = (MotionEvent) event;
+ final int displayId = motionEvent.getDisplayId();
final float newX = motionEvent.getRawX();
final float newY = motionEvent.getRawY();
final boolean isStylusButtonDown =
@@ -102,7 +103,8 @@ class DragInputEventReceiver extends InputEventReceiver {
return;
}
- mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+ mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, displayId, newX,
+ newY);
handled = true;
} catch (Exception e) {
Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 1c4e487d2e7e..3a0e41a5f9f8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -113,8 +113,8 @@ class DragState {
boolean mRelinquishDragSurfaceToDropTarget;
float mAnimatedScale = 1.0f;
float mOriginalAlpha;
- float mOriginalX, mOriginalY;
- float mCurrentX, mCurrentY;
+ float mOriginalDisplayX, mOriginalDisplayY;
+ float mCurrentDisplayX, mCurrentDisplayY;
float mThumbOffsetX, mThumbOffsetY;
InputInterceptor mInputInterceptor;
ArrayList<WindowState> mNotifiedWindows;
@@ -230,22 +230,22 @@ class DragState {
if (mDragInProgress) {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Broadcasting DRAG_ENDED");
for (WindowState ws : mNotifiedWindows) {
- float x = 0;
- float y = 0;
+ float inWindowX = 0;
+ float inWindowY = 0;
SurfaceControl dragSurface = null;
if (!mDragResult && (ws.mSession.mPid == mPid)) {
// Report unconsumed drop location back to the app that started the drag.
- x = ws.translateToWindowX(mCurrentX);
- y = ws.translateToWindowY(mCurrentY);
+ inWindowX = ws.translateToWindowX(mCurrentDisplayX);
+ inWindowY = ws.translateToWindowY(mCurrentDisplayY);
if (relinquishDragSurfaceToDragSource()) {
// If requested (and allowed), report the drag surface back to the app
// starting the drag to handle the return animation
dragSurface = mSurfaceControl;
}
}
- DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, x, y,
- mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null, dragSurface, null,
- mDragResult);
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, inWindowX,
+ inWindowY, mThumbOffsetX, mThumbOffsetY, mFlags, null, null, null,
+ dragSurface, null, mDragResult);
try {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
ws.mClient.dispatchDragEvent(event);
@@ -297,70 +297,71 @@ class DragState {
}
/**
- * Creates the drop event for this drag gesture. If `touchedWin` is null, then the drop event
- * will be created for dispatching to the unhandled drag and the drag surface will be provided
- * as a part of the dispatched event.
+ * Creates the drop event for dispatching to the unhandled drag.
+ * TODO(b/384841906): Update `inWindowX` and `inWindowY` to be display-coordinate.
*/
- private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
- boolean includePrivateInfo) {
- if (touchedWin != null) {
- final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
- final DragAndDropPermissionsHandler dragAndDropPermissions;
- if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
- && mData != null) {
- dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
- mData,
- mUid,
- touchedWin.getOwningPackage(),
- mFlags & DRAG_FLAGS_URI_PERMISSIONS,
- mSourceUserId,
- targetUserId);
- } else {
- dragAndDropPermissions = null;
- }
- if (mSourceUserId != targetUserId) {
- if (mData != null) {
- mData.fixUris(mSourceUserId);
- }
- }
- final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
- /* includeDragSurface= */ targetInterceptsGlobalDrag,
- /* includeDragFlags= */ targetInterceptsGlobalDrag,
- dragAndDropPermissions);
+ private DragEvent createUnhandledDropEvent(float inWindowX, float inWindowY) {
+ return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData,
+ /* includeDragSurface= */ true,
+ /* includeDragFlags= */ true, null /* dragAndDropPermissions */);
+ }
+
+ /**
+ * Creates the drop event for this drag gesture.
+ */
+ private DragEvent createDropEvent(float inWindowX, float inWindowY, WindowState touchedWin) {
+ final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+ final DragAndDropPermissionsHandler dragAndDropPermissions;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+ && mData != null) {
+ dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock, mData,
+ mUid, touchedWin.getOwningPackage(), mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+ mSourceUserId, targetUserId);
} else {
- return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mDataDescription, mData,
- /* includeDragSurface= */ includePrivateInfo,
- /* includeDragFlags= */ includePrivateInfo,
- null /* dragAndDropPermissions */);
+ dragAndDropPermissions = null;
}
+ if (mSourceUserId != targetUserId) {
+ if (mData != null) {
+ mData.fixUris(mSourceUserId);
+ }
+ }
+ final boolean targetInterceptsGlobalDrag = targetInterceptsGlobalDrag(touchedWin);
+ return obtainDragEvent(DragEvent.ACTION_DROP, inWindowX, inWindowY, mDataDescription, mData,
+ /* includeDragSurface= */ targetInterceptsGlobalDrag,
+ /* includeDragFlags= */ targetInterceptsGlobalDrag, dragAndDropPermissions);
}
/**
* Notify the drop target and tells it about the data. If the drop event is not sent to the
* target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to
* handle the drop.
+ * @param inWindowX if `token` refers to a dragEvent-accepting window, `inWindowX` will be
+ * inside the window, else values might be invalid (0, 0).
+ * @param inWindowY if `token` refers to a dragEvent-accepting window, `inWindowY` will be
+ * inside the window, else values might be invalid (0, 0).
*/
- boolean reportDropWindowLock(IBinder token, float x, float y) {
+ boolean reportDropWindowLock(IBinder token, float inWindowX, float inWindowY) {
if (mAnimator != null) {
return false;
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DROP");
- return reportDropWindowLockInner(token, x, y);
+ return reportDropWindowLockInner(token, inWindowX, inWindowY);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
- private boolean reportDropWindowLockInner(IBinder token, float x, float y) {
+ private boolean reportDropWindowLockInner(IBinder token, float inWindowX, float inWindowY) {
if (mAnimator != null) {
return false;
}
final WindowState touchedWin = mService.mInputToWindowMap.get(token);
- final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
- true /* includePrivateInfo */);
+ // TODO(b/384841906): The x, y here when sent to a window and unhandled, will still be
+ // relative to the window it was originally sent to. Need to update this to actually be
+ // display-coordinate.
+ final DragEvent unhandledDropEvent = createUnhandledDropEvent(inWindowX, inWindowY);
if (!isWindowNotified(touchedWin)) {
// Delegate to the unhandled drag listener as a first pass
if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
@@ -381,7 +382,7 @@ class DragState {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin);
final IBinder clientToken = touchedWin.mClient.asBinder();
- final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
+ final DragEvent event = createDropEvent(inWindowX, inWindowY, touchedWin);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DragDropController#dispatchDrop");
touchedWin.mClient.dispatchDragEvent(event);
@@ -486,8 +487,8 @@ class DragState {
*/
void broadcastDragStartedLocked(final float touchX, final float touchY) {
Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
- mOriginalX = mCurrentX = touchX;
- mOriginalY = mCurrentY = touchY;
+ mOriginalDisplayX = mCurrentDisplayX = touchX;
+ mOriginalDisplayY = mCurrentDisplayY = touchY;
// Cache a base-class instance of the clip metadata so that parceling
// works correctly in calling out to the apps.
@@ -636,7 +637,7 @@ class DragState {
if (isWindowNotified(newWin)) {
return;
}
- sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
+ sendDragStartedLocked(newWin, mCurrentDisplayX, mCurrentDisplayY,
containsApplicationExtras(mDataDescription));
}
}
@@ -685,12 +686,21 @@ class DragState {
mAnimator = createCancelAnimationLocked();
}
- void updateDragSurfaceLocked(boolean keepHandling, float x, float y) {
+ /**
+ * Updates the position of the drag surface.
+ *
+ * @param keepHandling whether to keep handling the drag.
+ * @param displayId the display ID of the drag surface.
+ * @param displayX the x-coordinate of the drag surface in the display's coordinate frame.
+ * @param displayY the y-coordinate of the drag surface in the display's coordinate frame.
+ */
+ void updateDragSurfaceLocked(boolean keepHandling, int displayId, float displayX,
+ float displayY) {
if (mAnimator != null) {
return;
}
- mCurrentX = x;
- mCurrentY = y;
+ mCurrentDisplayX = displayX;
+ mCurrentDisplayY = displayY;
if (!keepHandling) {
return;
@@ -700,9 +710,10 @@ class DragState {
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
}
- mTransaction.setPosition(mSurfaceControl, x - mThumbOffsetX, y - mThumbOffsetY).apply();
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: pos=(%d,%d)", mSurfaceControl,
- (int) (x - mThumbOffsetX), (int) (y - mThumbOffsetY));
+ mTransaction.setPosition(mSurfaceControl, displayX - mThumbOffsetX,
+ displayY - mThumbOffsetY).apply();
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: displayId=%d, pos=(%d,%d)", mSurfaceControl,
+ displayId, (int) (displayX - mThumbOffsetX), (int) (displayY - mThumbOffsetY));
}
/**
@@ -713,6 +724,12 @@ class DragState {
return mDragInProgress;
}
+ /**
+ * `x` and `y` here varies between local window coordinate, relative coordinate to another
+ * window and local display coordinate, all depending on the `action`. Please take a look
+ * at the callers to determine the type.
+ * TODO(b/384845022): Properly document the events sent based on the event type.
+ */
private DragEvent obtainDragEvent(int action, float x, float y, ClipDescription description,
ClipData data, boolean includeDragSurface, boolean includeDragFlags,
IDragAndDropPermissions dragAndDropPermissions) {
@@ -728,34 +745,34 @@ class DragState {
final long duration;
if (mCallingTaskIdToHide != -1) {
animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentDisplayX,
+ mCurrentDisplayX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentDisplayY,
+ mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
duration = MIN_ANIMATION_DURATION_MS;
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
- mOriginalX - mThumbOffsetX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
- mOriginalY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+ mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+ mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha,
+ mOriginalAlpha / 2));
- final float translateX = mOriginalX - mCurrentX;
- final float translateY = mOriginalY - mCurrentY;
+ final float translateX = mOriginalDisplayX - mCurrentDisplayX;
+ final float translateY = mOriginalDisplayY - mCurrentDisplayY;
// Adjust the duration to the travel distance.
final double travelDistance = Math.sqrt(
translateX * translateX + translateY * translateY);
- final double displayDiagonal =
- Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
- duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
- * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
+ final double displayDiagonal = Math.sqrt(
+ mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
+ duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal * (
+ MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
}
final AnimationListener listener = new AnimationListener();
@@ -771,18 +788,20 @@ class DragState {
private ValueAnimator createCancelAnimationLocked() {
final ValueAnimator animator;
if (mCallingTaskIdToHide != -1) {
- animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentX, mCurrentX),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentY, mCurrentY),
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X, mCurrentDisplayX,
+ mCurrentDisplayX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y, mCurrentDisplayY,
+ mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
- PropertyValuesHolder.ofFloat(
- ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+ mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+ mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
}
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 7af67e63f469..c60d3677319a 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -20,41 +20,51 @@ import static com.android.server.wm.AnimationSpecProto.WINDOW;
import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
import android.annotation.NonNull;
-import android.content.Context;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
-import com.android.internal.R;
-
import java.io.PrintWriter;
/**
* An animation controller to fade-in/out for a window token.
*/
public class FadeAnimationController {
+ static final int SHORT_DURATION_MS = 200;
+ static final int MEDIUM_DURATION_MS = 350;
+
protected final DisplayContent mDisplayContent;
- protected final Context mContext;
public FadeAnimationController(DisplayContent displayContent) {
mDisplayContent = displayContent;
- mContext = displayContent.mWmService.mContext;
}
/**
* @return a fade-in Animation.
*/
public Animation getFadeInAnimation() {
- return AnimationUtils.loadAnimation(mContext, R.anim.fade_in);
+ final AlphaAnimation anim = new AlphaAnimation(0f, 1f);
+ anim.setDuration(getScaledDuration(MEDIUM_DURATION_MS));
+ anim.setInterpolator(new DecelerateInterpolator());
+ return anim;
}
/**
* @return a fade-out Animation.
*/
public Animation getFadeOutAnimation() {
- return AnimationUtils.loadAnimation(mContext, R.anim.fade_out);
+ final AlphaAnimation anim = new AlphaAnimation(1f, 0f);
+ anim.setDuration(getScaledDuration(SHORT_DURATION_MS));
+ anim.setInterpolator(new AccelerateInterpolator());
+ return anim;
+ }
+
+ long getScaledDuration(int durationMs) {
+ return (long) (durationMs * mDisplayContent.mWmService.getWindowAnimationScaleLocked());
}
/** Run the fade in/out animation for a window token. */
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index cf145f94f787..ce8518449230 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -374,12 +374,6 @@ class InsetsStateController {
void notifyControlChanged(InsetsControlTarget target, InsetsSourceProvider provider) {
addToPendingControlMaps(target, provider);
notifyPendingInsetsControlChanged();
-
- if (android.view.inputmethod.Flags.refactorInsetsController()) {
- notifyInsetsChanged();
- mDisplayContent.updateSystemGestureExclusion();
- mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
- }
}
void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3d2868540334..865d5facc4a4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2926,7 +2926,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void prepareForShutdown() {
- mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
for (int i = 0; i < getChildCount(); i++) {
createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
index 38e011509885..efc68aac0323 100644
--- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -95,8 +95,9 @@ public class ScreenRecordingCallbackController {
if (mediaProjectionInfo.getLaunchCookie() == null) {
mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
} else {
- mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
- == mediaProjectionInfo.getLaunchCookie().binder).getTask();
+ final ActivityRecord matchingActivity = mWms.mRoot.getActivity(activity ->
+ activity.mLaunchCookie == mediaProjectionInfo.getLaunchCookie().binder);
+ mRecordedWC = matchingActivity != null ? matchingActivity.getTask() : null;
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9062afb50acb..d92301ba4f6f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3707,10 +3707,21 @@ class Task extends TaskFragment {
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
- if (taskFragment != null && taskFragment.isEmbedded()) {
- final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
- if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
- adjacentTf.assignLayer(t, layer++);
+ if (taskFragment != null && taskFragment.isEmbedded()
+ && taskFragment.hasAdjacentTaskFragment()) {
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ final int[] nextLayer = { layer };
+ taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+ if (adjacentTf.shouldBoostDimmer()) {
+ adjacentTf.assignLayer(t, nextLayer[0]++);
+ }
+ });
+ layer = nextLayer[0];
+ } else {
+ final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTf.shouldBoostDimmer()) {
+ adjacentTf.assignLayer(t, layer++);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9564c5959d98..3d0b41ba3a0f 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1045,7 +1045,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
+ adjacentFlagRootTask);
}
- if (adjacentFlagRootTask.getAdjacentTaskFragment() == null) {
+ if (!adjacentFlagRootTask.hasAdjacentTaskFragment()) {
throw new UnsupportedOperationException(
"Can't set non-adjacent root as launch adjacent flag root tr="
+ adjacentFlagRootTask);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 38a2ebeba332..7d300e98f44b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -36,7 +36,9 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.window.flags.Flags;
+import java.util.ArrayList;
import java.util.Set;
/**
@@ -154,6 +156,8 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
* The attributes of task snapshot are based on task configuration. But sometimes the
* configuration may have been changed during a transition, so supply the ChangeInfo that
* stored the previous appearance of the closing task.
+ *
+ * The snapshot won't be created immediately if it should be captured as fake snapshot.
*/
void recordSnapshot(Task task, Transition.ChangeInfo changeInfo) {
mCurrentChangeInfo = changeInfo;
@@ -164,13 +168,35 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
}
}
- TaskSnapshot recordSnapshot(Task task) {
- final TaskSnapshot snapshot = recordSnapshotInner(task);
- if (snapshot != null && !task.isActivityTypeHome()) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- task.onSnapshotChanged(snapshot);
+ void recordSnapshot(Task task) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
+ final SnapshotSupplier supplier = getRecordSnapshotSupplier(task);
+ if (supplier == null) {
+ return;
}
- return snapshot;
+ final int mode = getSnapshotMode(task);
+ if (Flags.excludeDrawingAppThemeSnapshotFromLock() && mode == SNAPSHOT_MODE_APP_THEME) {
+ mService.mH.post(supplier::handleSnapshot);
+ } else {
+ supplier.handleSnapshot();
+ }
+ }
+
+ /**
+ * Note that the snapshot is not created immediately, if the returned supplier is non-null, the
+ * caller must call {@link AbsAppSnapshotController.SnapshotSupplier#get} or
+ * {@link AbsAppSnapshotController.SnapshotSupplier#handleSnapshot} to complete the entire
+ * record request.
+ */
+ SnapshotSupplier getRecordSnapshotSupplier(Task task) {
+ return recordSnapshotInner(task, true /* allowAppTheme */, snapshot -> {
+ if (!task.isActivityTypeHome()) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ task.onSnapshotChanged(snapshot);
+ }
+ });
}
/**
@@ -328,27 +354,38 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
* Record task snapshots before shutdown.
*/
void prepareShutdown() {
- if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+ if (!Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- // Make write items run in a batch.
- mPersister.mSnapshotPersistQueue.setPaused(true);
- mPersister.mSnapshotPersistQueue.prepareShutdown();
- for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
- mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
- if (task.isVisible() && !task.isActivityTypeHome()) {
- final TaskSnapshot snapshot = captureSnapshot(task);
- if (snapshot != null) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ final ArrayList<SnapshotSupplier> supplierArrayList = new ArrayList<>();
+ synchronized (mService.mGlobalLock) {
+ // Make write items run in a batch.
+ mPersister.mSnapshotPersistQueue.setPaused(true);
+ mPersister.mSnapshotPersistQueue.prepareShutdown();
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final SnapshotSupplier supplier = captureSnapshot(task,
+ true /* allowAppTheme */);
+ if (supplier != null) {
+ supplier.setConsumer(t ->
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, t));
+ supplierArrayList.add(supplier);
+ }
}
- }
- }, true /* traverseTopToBottom */);
+ }, true /* traverseTopToBottom */);
+ }
+ }
+ for (int i = supplierArrayList.size() - 1; i >= 0; --i) {
+ supplierArrayList.get(i).handleSnapshot();
+ }
+ synchronized (mService.mGlobalLock) {
+ mPersister.mSnapshotPersistQueue.setPaused(false);
}
- mPersister.mSnapshotPersistQueue.setPaused(false);
}
void waitFlush(long timeout) {
- if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
+ if (!Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
mPersister.mSnapshotPersistQueue.waitFlush(timeout);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1f539a129e7d..a3d71dbc5ed1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1589,7 +1589,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
cleanUpInternal();
// Handle back animation if it's already started.
- mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
+ mController.mAtm.mBackNavigationController.onTransitionFinish(this);
mController.mFinishingTransition = null;
mController.mSnapshotController.onTransitionFinish(mType, mTargets);
// Resume snapshot persist thread after snapshot controller analysis this transition.
@@ -2542,15 +2542,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// TaskFragment doesn't contain occluded ActivityRecord.
return true;
}
- final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
- if (adjacentTaskFragment != null) {
- // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
- // hidden unless any of them are translucent.
- return adjacentTaskFragment.isTranslucentForTransition();
- } else {
+ if (!taskFragment.hasAdjacentTaskFragment()) {
// Non-filling without adjacent is considered as translucent.
return !wc.fillsParent();
}
+ // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be
+ // hidden unless any of them are translucent.
+ if (!Flags.allowMultipleAdjacentTaskFragments()) {
+ return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition();
+ }
+ return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition);
}
private void updatePriorVisibility() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d9c53dfe0f4..04650b9e0150 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2945,7 +2945,7 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null) {
if (callingPid != MY_PID) {
- throw new WindowManager.InvalidDisplayException(
+ throw new IllegalArgumentException(
"attachWindowContextToDisplayContent: trying to attach to a"
+ " non-existing display:" + displayId);
}
@@ -10084,14 +10084,16 @@ public class WindowManagerService extends IWindowManager.Stub
TaskSnapshot taskSnapshot;
final long token = Binder.clearCallingIdentity();
try {
+ final Supplier<TaskSnapshot> supplier;
synchronized (mGlobalLock) {
Task task = mRoot.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
if (task == null) {
throw new IllegalArgumentException(
"Failed to find matching task for taskId=" + taskId);
}
- taskSnapshot = mTaskSnapshotController.captureSnapshot(task);
+ supplier = mTaskSnapshotController.captureSnapshot(task, true /* allowAppTheme */);
}
+ taskSnapshot = supplier != null ? supplier.get() : null;
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 60130d1f97be..f10b7b9a95a4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -91,6 +91,7 @@ import android.server.ServerProtoEnums;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
+import android.tracing.perfetto.InitArguments;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Dumpable;
@@ -792,6 +793,12 @@ public final class SystemServer implements Dumpable {
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
+ if (android.tracing.Flags.systemServerLargePerfettoShmemBuffer()) {
+ // Explicitly initialize a 4 MB shmem buffer for Perfetto producers (b/382369925)
+ android.tracing.perfetto.Producer.init(new InitArguments(
+ InitArguments.PERFETTO_BACKEND_SYSTEM, 4 * 1024));
+ }
+
t.traceBegin("InitBeforeStartServices");
// Record the process start information in sys props.
@@ -3114,10 +3121,10 @@ public final class SystemServer implements Dumpable {
if (com.android.ranging.flags.Flags.rangingStackEnabled()) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
|| context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)
+ PackageManager.FEATURE_WIFI_AWARE)
|| (com.android.ranging.flags.Flags.rangingCsEnabled()
&& context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) {
+ PackageManager.FEATURE_BLUETOOTH_LE))) {
t.traceBegin("RangingService");
// TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
try {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index d2c91ff2ef60..232bb83fdf9f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -286,14 +286,21 @@ class AppIdPermissionPolicy : SchemePolicy() {
return@forEach
}
var newFlags = oldFlags
+ val isSystemOrInstalled =
+ packageState.isSystem || packageState.getUserStateOrDefault(userId).isInstalled
newFlags =
if (
- newFlags.hasBits(PermissionFlags.ROLE) ||
- newFlags.hasBits(PermissionFlags.PREGRANT)
+ isSystemOrInstalled && (
+ newFlags.hasBits(PermissionFlags.ROLE) ||
+ newFlags.hasBits(PermissionFlags.PREGRANT)
+ )
) {
newFlags or PermissionFlags.RUNTIME_GRANTED
} else {
- newFlags andInv PermissionFlags.RUNTIME_GRANTED
+ newFlags andInv (
+ PermissionFlags.RUNTIME_GRANTED or PermissionFlags.ROLE or
+ PermissionFlags.PREGRANT
+ )
}
newFlags = newFlags andInv USER_SETTABLE_MASK
if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 12370954e9a5..8b357862dcbc 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -72,7 +72,8 @@ class AppIdPermissionPolicyPermissionResetTest : BasePermissionPolicyTest() {
} else {
mockPackageState(
APP_ID_1,
- mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+ mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)),
+ true
)
}
setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index d00e2c677930..1f45792e5097 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -33,6 +33,7 @@ import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.os.BinderProxy;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -290,11 +291,15 @@ public class DisplayEventDeliveryTest {
}
/**
- * Return true if the freezer is enabled on this platform.
+ * Return true if the freezer is enabled on this platform and if freezer notifications are
+ * supported. It is not enough to test that the freezer notification feature is enabled
+ * because some devices do not have the necessary kernel support.
*/
private boolean isAppFreezerEnabled() {
try {
- return mActivityManager.getService().isAppFreezerEnabled();
+ return mActivityManager.getService().isAppFreezerEnabled()
+ && android.os.Flags.binderFrozenStateChangeCallback()
+ && BinderProxy.isFrozenStateChangeCallbackSupported();
} catch (Exception e) {
Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
return false;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index f96294ed4ca8..b7b4f0424165 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -214,7 +214,8 @@ public class DisplayManagerServiceTest {
private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
private static final long STANDARD_DISPLAY_EVENTS =
DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
| DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
STANDARD_DISPLAY_EVENTS
@@ -222,7 +223,7 @@ public class DisplayManagerServiceTest {
private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
- private static final String EVENT_DISPLAY_CHANGED = "EVENT_DISPLAY_CHANGED";
+ private static final String EVENT_DISPLAY_BASIC_CHANGED = "EVENT_DISPLAY_BASIC_CHANGED";
private static final String EVENT_DISPLAY_BRIGHTNESS_CHANGED =
"EVENT_DISPLAY_BRIGHTNESS_CHANGED";
private static final String EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED =
@@ -889,7 +890,6 @@ public class DisplayManagerServiceTest {
FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
displayManager, bs, displayDevice);
-
// Simulate DisplayDevice change
DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
displayDeviceInfo2.copyFrom(displayDeviceInfo);
@@ -900,7 +900,8 @@ public class DisplayManagerServiceTest {
Handler handler = displayManager.getDisplayHandler();
waitForIdleHandler(handler);
- assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_BASIC_CHANGED,
+ EVENT_DISPLAY_REFRESH_RATE_CHANGED);
}
/**
@@ -2145,7 +2146,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2154,7 +2155,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(1234, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2163,7 +2164,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2172,7 +2173,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2180,7 +2181,7 @@ public class DisplayManagerServiceTest {
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
}
/**
@@ -2303,16 +2304,16 @@ public class DisplayManagerServiceTest {
updateRenderFrameRate(displayManager, displayDevice, 30f);
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateRenderFrameRate(displayManager, displayDevice, 30f);
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
updateRenderFrameRate(displayManager, displayDevice, 20f);
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
}
@@ -3888,7 +3889,7 @@ public class DisplayManagerServiceTest {
observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
waitForIdleHandler(handler);
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
}
@Test
@@ -3919,7 +3920,7 @@ public class DisplayManagerServiceTest {
observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
waitForIdleHandler(handler);
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
}
private void initDisplayPowerController(DisplayManagerInternal localService) {
@@ -4389,8 +4390,8 @@ public class DisplayManagerServiceTest {
return EVENT_DISPLAY_ADDED;
case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
return EVENT_DISPLAY_REMOVED;
- case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
- return EVENT_DISPLAY_CHANGED;
+ case DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED:
+ return EVENT_DISPLAY_BASIC_CHANGED;
case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
return EVENT_DISPLAY_BRIGHTNESS_CHANGED;
case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 5d427139a857..c65024f8f9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -26,6 +26,7 @@ import org.junit.Test
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -43,7 +44,7 @@ class DisplayTopologyCoordinatorTest {
@Before
fun setUp() {
- displayInfo.displayId = 2
+ displayInfo.displayId = Display.DEFAULT_DISPLAY
displayInfo.logicalWidth = 300
displayInfo.logicalHeight = 200
displayInfo.logicalDensityDpi = 100
@@ -90,6 +91,44 @@ class DisplayTopologyCoordinatorTest {
}
@Test
+ fun updateDisplay() {
+ whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
+ .thenReturn(true)
+
+ coordinator.onDisplayChanged(displayInfo)
+
+ verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
+ }
+
+ @Test
+ fun updateDisplay_notChanged() {
+ whenever(mockTopology.updateDisplay(eq(Display.DEFAULT_DISPLAY), anyFloat(), anyFloat()))
+ .thenReturn(false)
+
+ coordinator.onDisplayChanged(displayInfo)
+
+ verify(mockTopologyChangedCallback, never()).invoke(any())
+ }
+
+ @Test
+ fun removeDisplay() {
+ whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(true)
+
+ coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
+
+ verify(mockTopologyChangedCallback).invoke(mockTopologyCopy)
+ }
+
+ @Test
+ fun removeDisplay_notChanged() {
+ whenever(mockTopology.removeDisplay(Display.DEFAULT_DISPLAY)).thenReturn(false)
+
+ coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY)
+
+ verify(mockTopologyChangedCallback, never()).invoke(any())
+ }
+
+ @Test
fun getTopology_copy() {
assertThat(coordinator.topology).isEqualTo(mockTopologyCopy)
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index ad30f22fe060..0dbb6ba58b3c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -36,9 +36,9 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DE
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE;
@@ -1170,18 +1170,20 @@ public class LogicalDisplayMapperTest {
@Test
public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() {
- // Change the display state
+ // Change the refresh rate override
DisplayInfo newDisplayInfo = new DisplayInfo();
- newDisplayInfo.state = STATE_OFF;
- assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+ newDisplayInfo.refreshRateOverride = 30;
+ assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
- // Change the refresh rate override
+ // Change the display state
+ when(mFlagsMock.isDisplayListenerPerformanceImprovementsEnabled()).thenReturn(true);
newDisplayInfo = new DisplayInfo();
- newDisplayInfo.refreshRateOverride = 30;
- assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
+ newDisplayInfo.state = STATE_OFF;
+ assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
// Change multiple properties
newDisplayInfo = new DisplayInfo();
newDisplayInfo.refreshRateOverride = 30;
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 35f421e582d8..de6f9bd7527a 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -78,12 +78,15 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.power.hint.HintManagerService.AppHintSession;
import com.android.server.power.hint.HintManagerService.Injector;
import com.android.server.power.hint.HintManagerService.NativeWrapper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -93,6 +96,8 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
@@ -111,6 +116,7 @@ import java.util.concurrent.locks.LockSupport;
*/
public class HintManagerServiceTest {
private static final String TAG = "HintManagerServiceTest";
+ private List<File> mFilesCreated = new ArrayList<>();
private static WorkDuration makeWorkDuration(
long timestamp, long duration, long workPeriodStartTime,
@@ -192,9 +198,9 @@ public class HintManagerServiceTest {
mSupportInfo.sessionTags = -1;
mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
mSupportInfo.headroom.isCpuSupported = true;
- mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
+ mSupportInfo.headroom.cpuMinIntervalMillis = 1000;
mSupportInfo.headroom.isGpuSupported = true;
- mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
+ mSupportInfo.headroom.gpuMinIntervalMillis = 1000;
mSupportInfo.compositionData = new SupportInfo.CompositionDataSupportInfo();
return mSupportInfo;
}
@@ -243,6 +249,13 @@ public class HintManagerServiceTest {
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
}
+ @After
+ public void tearDown() {
+ for (File file : mFilesCreated) {
+ file.delete();
+ }
+ }
+
/**
* Mocks the creation calls, but without support for new createHintSessionWithConfig method
*/
@@ -1327,6 +1340,58 @@ public class HintManagerServiceTest {
});
}
+ @Test
+ public void testCpuHeadroomCpuProcStatPath() throws Exception {
+ File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
+ dir.mkdir();
+ String procStatFileStr = "mock_proc_stat";
+ File file = new File(dir, procStatFileStr);
+ mFilesCreated.add(file);
+ try (FileOutputStream output = new FileOutputStream(file)) {
+ output.write("cpu 2000 3000 4000 0 0 0 0 0 0 0".getBytes());
+ }
+ HintManagerService service = createService();
+ service.setProcStatPathOverride(file.getPath());
+
+ CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
+ CpuHeadroomParams halParams1 = new CpuHeadroomParams();
+ halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams1.tids = new int[]{Process.myPid()};
+
+ float headroom1 = 0.1f;
+ CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ // expire the cache but cpu proc hasn't changed so we expect no value return
+ Thread.sleep(1100);
+ clearInvocations(mIPowerMock);
+ assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+
+ // update user jiffies with 500 equivalent jiffies, which is not sufficient cpu time
+ Thread.sleep(1100);
+ try (FileOutputStream output = new FileOutputStream(file)) {
+ output.write(("cpu " + (2000 + (int) (500 / service.mJiffyMillis))
+ + " 3000 4000 0 0 0 0 0 0 0").getBytes());
+ }
+ clearInvocations(mIPowerMock);
+ assertEquals(null, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+
+ // update nice jiffies with 600 equivalent jiffies, now it exceeds 1000ms requirement
+ Thread.sleep(1100);
+ try (FileOutputStream output = new FileOutputStream(file)) {
+ output.write(("cpu " + (2000 + (int) (500 / service.mJiffyMillis))
+ + " " + +(3000 + (int) (600 / service.mJiffyMillis))
+ + " 4000 0 0 0 0 0 0 0").getBytes());
+ }
+ clearInvocations(mIPowerMock);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+ }
+
@Test
@EnableFlags({Flags.FLAG_CPU_HEADROOM_AFFINITY_CHECK})
@@ -1397,8 +1462,8 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
- // after 1 more second it should be served with cache still
- Thread.sleep(1000);
+ // after 500ms more it should be served with cache
+ Thread.sleep(500);
clearInvocations(mIPowerMock);
assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1410,8 +1475,8 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
- // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(1100);
+ // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
+ Thread.sleep(600);
clearInvocations(mIPowerMock);
assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
@@ -1519,8 +1584,8 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
- // after 1 more second it should be served with cache still
- Thread.sleep(1000);
+ // after 500ms it should be served with cache
+ Thread.sleep(500);
clearInvocations(mIPowerMock);
assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
@@ -1528,8 +1593,8 @@ public class HintManagerServiceTest {
verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
- // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(1100);
+ // after 1+ seconds it should be served from HAL as it exceeds 1000 millis interval
+ Thread.sleep(600);
clearInvocations(mIPowerMock);
assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 96741e0b1e87..469bd66b7e7b 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -21,6 +21,7 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -889,6 +890,32 @@ public class NotifierTest {
"my.package.name", false, null, null);
}
+ @Test
+ public void getWakelockMonitorTypeForLogging_evaluatesWakelockLevel() {
+ createNotifier();
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.SCREEN_DIM_WAKE_LOCK),
+ PowerManager.FULL_WAKE_LOCK);
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK), PowerManager.FULL_WAKE_LOCK);
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.DRAW_WAKE_LOCK),
+ PowerManager.DRAW_WAKE_LOCK);
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK),
+ PowerManager.PARTIAL_WAKE_LOCK);
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK),
+ PowerManager.PARTIAL_WAKE_LOCK);
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.DOZE_WAKE_LOCK), -1);
+
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
+ .thenReturn(true);
+
+ createNotifier();
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1);
+ }
+
private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 2fe6918630f6..7dbbff22a537 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -33,6 +33,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.server.am.UserController.CLEAR_USER_JOURNEY_SESSION_MSG;
import static com.android.server.am.UserController.COMPLETE_USER_SWITCH_MSG;
import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS;
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
@@ -94,6 +95,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -181,14 +183,12 @@ public class UserControllerTest {
Intent.ACTION_USER_STARTING);
private static final Set<Integer> START_FOREGROUND_USER_MESSAGE_CODES = newHashSet(
- 0, // for startUserInternalOnHandler
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
USER_CURRENT_MSG);
private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
- 0, // for startUserInternalOnHandler
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
@@ -376,7 +376,7 @@ public class UserControllerTest {
// and the cascade effect goes on...). In fact, a better approach would to not assert the
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
- .containsExactly(/* for startUserInternalOnHandler */ 0, USER_START_MSG);
+ .containsExactly(USER_START_MSG);
}
private void startUserAssertions(
@@ -419,17 +419,12 @@ public class UserControllerTest {
@Test
public void testDispatchUserSwitch() throws RemoteException {
// Prepare mock observer and register it
- IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
- when(observer.asBinder()).thenReturn(new Binder());
- doAnswer(invocation -> {
- IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
- callback.sendResult(null);
- return null;
- }).when(observer).onUserSwitching(anyInt(), any());
- mUserController.registerUserSwitchObserver(observer, "mock");
+ IUserSwitchObserver observer = registerUserSwitchObserver(
+ /* replyToOnBeforeUserSwitchingCallback= */ true,
+ /* replyToOnUserSwitchingCallback= */ true);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -453,14 +448,26 @@ public class UserControllerTest {
}
@Test
+ public void testShouldCrashWhenOnBeforeUserSwitchingTimeouts() throws RemoteException {
+ IUserSwitchObserver observer = registerUserSwitchObserver(
+ /* replyToOnBeforeUserSwitchingCallback= */ false,
+ /* replyToOnUserSwitchingCallback= */ true);
+ mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
+ assertThrows("Should have crashed when observers don't reply to onBeforeUserSwitching in "
+ + DEFAULT_BEFORE_USER_SWITCH_TIMEOUT_MS + " ms", RuntimeException.class,
+ mInjector.mHandler::runPendingCallbacks);
+ }
+
+ @Test
public void testDispatchUserSwitchBadReceiver() throws RemoteException {
- // Prepare mock observer which doesn't notify the callback and register it
- IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
- when(observer.asBinder()).thenReturn(new Binder());
- mUserController.registerUserSwitchObserver(observer, "mock");
+ // Prepare mock observer which doesn't notify the onUserSwitching callback and register it
+ IUserSwitchObserver observer = registerUserSwitchObserver(
+ /* replyToOnBeforeUserSwitchingCallback= */ true,
+ /* replyToOnUserSwitchingCallback= */ false);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
- verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID));
+ verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID), any());
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -551,7 +558,6 @@ public class UserControllerTest {
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
if (backgroundUserStopping) {
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
- expectedCodes.add(0); // this is for directly posting in stopping.
}
if (expectScheduleBackgroundUserStopping) {
expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
@@ -567,9 +573,9 @@ public class UserControllerTest {
@Test
public void testDispatchUserSwitchComplete() throws RemoteException {
// Prepare mock observer and register it
- IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
- when(observer.asBinder()).thenReturn(new Binder());
- mUserController.registerUserSwitchObserver(observer, "mock");
+ IUserSwitchObserver observer = registerUserSwitchObserver(
+ /* replyToOnBeforeUserSwitchingCallback= */ true,
+ /* replyToOnUserSwitchingCallback= */ true);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1752,6 +1758,29 @@ public class UserControllerTest {
verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
}
+ private IUserSwitchObserver registerUserSwitchObserver(
+ boolean replyToOnBeforeUserSwitchingCallback, boolean replyToOnUserSwitchingCallback)
+ throws RemoteException {
+ IUserSwitchObserver observer = mock(IUserSwitchObserver.class);
+ when(observer.asBinder()).thenReturn(new Binder());
+ if (replyToOnBeforeUserSwitchingCallback) {
+ doAnswer(invocation -> {
+ IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+ callback.sendResult(null);
+ return null;
+ }).when(observer).onBeforeUserSwitching(anyInt(), any());
+ }
+ if (replyToOnUserSwitchingCallback) {
+ doAnswer(invocation -> {
+ IRemoteCallback callback = (IRemoteCallback) invocation.getArguments()[1];
+ callback.sendResult(null);
+ return null;
+ }).when(observer).onUserSwitching(anyInt(), any());
+ }
+ mUserController.registerUserSwitchObserver(observer, "mock");
+ return observer;
+ }
+
// Should be public to allow mocking
private static class TestInjector extends UserController.Injector {
public final TestHandler mHandler;
@@ -1957,6 +1986,7 @@ public class UserControllerTest {
* fix this, but in the meantime, this is your warning.
*/
private final List<Message> mMessages = new ArrayList<>();
+ private final List<Runnable> mPendingCallbacks = new ArrayList<>();
TestHandler(Looper looper) {
super(looper);
@@ -1989,14 +2019,24 @@ public class UserControllerTest {
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- Message copy = new Message();
- copy.copyFrom(msg);
- mMessages.add(copy);
- if (msg.getCallback() != null) {
- msg.getCallback().run();
+ if (msg.getCallback() == null) {
+ Message copy = new Message();
+ copy.copyFrom(msg);
+ mMessages.add(copy);
+ } else {
+ if (SystemClock.uptimeMillis() >= uptimeMillis) {
+ msg.getCallback().run();
+ } else {
+ mPendingCallbacks.add(msg.getCallback());
+ }
msg.setCallback(null);
}
return super.sendMessageAtTime(msg, uptimeMillis);
}
+
+ private void runPendingCallbacks() {
+ mPendingCallbacks.forEach(Runnable::run);
+ mPendingCallbacks.clear();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
index 9c61d95bc5e5..9528a05d38a0 100644
--- a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -23,13 +23,13 @@ import android.test.AndroidTestCase;
import android.util.Pair;
import android.util.Xml;
-import com.android.internal.util.FastXmlSerializer;
import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
@@ -123,8 +123,24 @@ public class CacheQuotaStrategyTest extends AndroidTestCase {
buildCacheQuotaHint("uuid2", 10, 250));
}
+ @Test
+ public void testReadInvalidInput() throws Exception {
+ String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota/>\n"
+ + "</cache-info>\n";
+
+ try {
+ CacheQuotaStrategy.readFromXml(new ByteArrayInputStream(
+ input.getBytes("UTF-8")));
+ fail("Expected XML parsing exception");
+ } catch (XmlPullParserException e) {
+ // Expected XmlPullParserException exception
+ }
+ }
+
private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) {
return new CacheQuotaHint.Builder()
.setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build();
}
-} \ No newline at end of file
+}
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 20f4bb65d27b..601023f89656 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -17683,4 +17683,145 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(mService.mNotificationList).isEmpty();
}
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testUnbundleNotification_ungrouped_restoresOriginalChannel() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+ // Post a single notification
+ final boolean hasOriginalSummary = false;
+ final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ final String keyToUnbundle = r.getKey();
+ mService.addNotification(r);
+
+ // Classify notification into the NEWS bundle
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(
+ r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r.applyAdjustments();
+ // Check that the NotificationRecord channel is updated
+ assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+ // Check that the Notification mChannelId is not updated
+ assertThat(r.getNotification().getChannelId()).isEqualTo(TEST_CHANNEL_ID);
+
+ // Unbundle the notification
+ mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+ // Check that the original channel was restored
+ assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+ verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary));
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testUnbundleNotification_grouped_restoresOriginalChannel() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+ // Post grouped notifications
+ final String originalGroupName = "originalGroup";
+ final int summaryId = 0;
+ final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 1, originalGroupName, false);
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 2, originalGroupName, false);
+ mService.addNotification(r2);
+ final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+ summaryId, originalGroupName, true);
+ mService.addNotification(summary);
+ final String originalGroupKey = summary.getGroupKey();
+ assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+ // Classify a child notification into the NEWS bundle
+ final String keyToUnbundle = r1.getKey();
+ final boolean hasOriginalSummary = true;
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals,
+ "", r1.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r1.applyAdjustments();
+ assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID);
+
+ // Unbundle the notification
+ mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+ // Check that the original channel was restored
+ assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+ verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testUnbundleNotification_groupedSummaryCanceled_restoresOriginalChannel()
+ throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+ when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
+ when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString())).thenReturn(true);
+
+ // Post grouped notifications
+ final String originalGroupName = "originalGroup";
+ final int summaryId = 0;
+ final NotificationRecord r1 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 1, originalGroupName, false);
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel,
+ summaryId + 2, originalGroupName, false);
+ mService.addNotification(r2);
+ final NotificationRecord summary = generateNotificationRecord(mTestNotificationChannel,
+ summaryId, originalGroupName, true);
+ mService.addNotification(summary);
+ final String originalGroupKey = summary.getGroupKey();
+ assertThat(mService.mSummaryByGroupKey).containsEntry(originalGroupKey, summary);
+
+ // Classify a child notification into the NEWS bundle
+ final String keyToUnbundle = r1.getKey();
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS);
+ Adjustment adjustment = new Adjustment(r1.getSbn().getPackageName(), r1.getKey(), signals,
+ "", r1.getUser().getIdentifier());
+ mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+ waitForIdle();
+ r1.applyAdjustments();
+ assertThat(r1.getChannel().getId()).isEqualTo(NEWS_ID);
+
+ // Cancel original summary
+ final boolean hasOriginalSummary = false;
+ mService.mSummaryByGroupKey.remove(summary.getGroupKey());
+
+ // Unbundle the notification
+ mService.mNotificationDelegate.unbundleNotification(keyToUnbundle);
+
+ // Check that the original channel was restored
+ assertThat(r1.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID);
+ verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary));
+ }
+
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index fbd53f714dbf..8e79514c875e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -66,7 +66,6 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
-import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
@@ -155,7 +154,6 @@ import android.util.proto.ProtoOutputStream;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -167,9 +165,6 @@ import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -204,6 +199,9 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
@@ -2640,6 +2638,35 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void getPackagesBypassingDnd_multipleUsers() {
+ int uidUser1 = UserHandle.getUid(1, UID_P);
+ NotificationChannel channelUser1Bypass = new NotificationChannel("id11", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channelUser1Bypass.setBypassDnd(true);
+ NotificationChannel channelUser1NoBypass = new NotificationChannel("id12", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ channelUser1NoBypass.setBypassDnd(false);
+
+ int uidUser2 = UserHandle.getUid(2, UID_P);
+ NotificationChannel channelUser2Bypass = new NotificationChannel("id21", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channelUser2Bypass.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1Bypass, true,
+ /* hasDndAccess= */ true, uidUser1, false);
+ mHelper.createNotificationChannel(PKG_P, uidUser1, channelUser1NoBypass, true,
+ /* hasDndAccess= */ true, uidUser1, false);
+ mHelper.createNotificationChannel(PKG_P, uidUser2, channelUser2Bypass, true,
+ /* hasDndAccess= */ true, uidUser2, false);
+
+ assertThat(mHelper.getPackagesBypassingDnd(0)).isEmpty();
+ assertThat(mHelper.getPackagesBypassingDnd(1))
+ .containsExactly(new ZenBypassingApp(PKG_P, false));
+ assertThat(mHelper.getPackagesBypassingDnd(2))
+ .containsExactly(new ZenBypassingApp(PKG_P, true));
+ }
+
+ @Test
public void getPackagesBypassingDnd_oneChannelBypassing_groupBlocked() {
int uid = UID_N_MR1;
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index a7fc10f2fcc5..948371f74a9c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
@@ -253,7 +254,11 @@ public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBa
*/
@Test
public void testSkipRecordActivity() {
- doReturn(createSnapshot()).when(mActivitySnapshotController).recordSnapshotInner(any());
+ final AbsAppSnapshotController.SnapshotSupplier supplier =
+ new AbsAppSnapshotController.SnapshotSupplier();
+ supplier.setSupplier(this::createSnapshot);
+ doReturn(supplier).when(mActivitySnapshotController).recordSnapshotInner(
+ any(), anyBoolean(), any());
final Task task = createTask(mDisplayContent);
mSnapshotPersistQueue.setPaused(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
index 1edbcd527bf4..463254caa845 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -23,14 +23,10 @@ import static org.mockito.Mockito.spy;
import android.compat.testing.PlatformCompatChangeRule;
import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
-import com.android.window.flags.Flags;
-
import junit.framework.Assert;
import org.junit.Rule;
@@ -125,8 +121,7 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
}
@Test
- @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+ public void testAllowReachabilityForThinLetterbox_disableForThinLetterboxing() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponent();
@@ -142,24 +137,6 @@ public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
});
}
- @Test
- @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
- runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
-
- robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
- robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
- robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
- robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
-
- robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
- robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
- robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
- robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
- });
- }
-
/**
* Runs a test scenario providing a Robot.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 429a396ad997..de4b6fac7abf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -150,8 +150,8 @@ public class DragDropControllerTests extends WindowTestsBase {
mProcess).build();
// Use a new TestIWindow so we don't collect events for other windows
- final WindowState window = createWindow(null, TYPE_BASE_APPLICATION, activity, name,
- ownerId, false, new TestIWindow());
+ final WindowState window = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
+ activity).setOwnerId(ownerId).setClientWindow(new TestIWindow()).build();
InputChannel channel = new InputChannel();
window.openInputChannel(channel);
window.mHasSurface = true;
@@ -249,7 +249,7 @@ public class DragDropControllerTests extends WindowTestsBase {
mTarget.mDeferDragStateClosed = true;
mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
// Verify the drop event includes the drag surface
- mTarget.handleMotionEvent(false, 0, 0);
+ mTarget.handleMotionEvent(false, mWindow.getDisplayId(), 0, 0);
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
assertTrue(dropEvent.getDragSurface() != null);
@@ -296,7 +296,7 @@ public class DragDropControllerTests extends WindowTestsBase {
0).getClipData().willParcelWithActivityInfo());
mTarget.reportDropWindow(globalInterceptWindow.mInputChannelToken, 0, 0);
- mTarget.handleMotionEvent(false, 0, 0);
+ mTarget.handleMotionEvent(false, globalInterceptWindow.getDisplayId(), 0, 0);
mToken = globalInterceptWindow.mClient.asBinder();
// Verify the drop event is only sent for the global intercept window
@@ -334,8 +334,8 @@ public class DragDropControllerTests extends WindowTestsBase {
try {
mTarget.mDeferDragStateClosed = true;
mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0);
- // // Verify the drop event does not have the drag flags
- mTarget.handleMotionEvent(false, 0, 0);
+ // Verify the drop event does not have the drag flags
+ mTarget.handleMotionEvent(false, mWindow.getDisplayId(), 0, 0);
final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1);
assertTrue(dropEvent.getDragFlags() == (View.DRAG_FLAG_GLOBAL
| View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG));
@@ -520,7 +520,7 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify after consuming that the drag surface is relinquished
mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
- mTarget.handleMotionEvent(false, 0, 0);
+ mTarget.handleMotionEvent(false, otherWindow.getDisplayId(), 0, 0);
mToken = otherWindow.mClient.asBinder();
mTarget.reportDropResult(otherIWindow, true);
@@ -551,7 +551,7 @@ public class DragDropControllerTests extends WindowTestsBase {
// Verify after consuming that the drag surface is relinquished
mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0);
- mTarget.handleMotionEvent(false, 0, 0);
+ mTarget.handleMotionEvent(false, otherWindow.getDisplayId(), 0, 0);
mToken = otherWindow.mClient.asBinder();
mTarget.reportDropResult(otherIWindow, false);
@@ -586,7 +586,8 @@ public class DragDropControllerTests extends WindowTestsBase {
ClipData.newPlainText("label", "Test"), () -> {
// Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(),
+ invalidXY, invalidXY);
mTarget.reportDropResult(mWindow.mClient, false);
mTarget.onUnhandledDropCallback(true);
mToken = null;
@@ -610,7 +611,8 @@ public class DragDropControllerTests extends WindowTestsBase {
ClipData.newPlainText("label", "Test"), () -> {
// Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(),
+ invalidXY, invalidXY);
mTarget.onUnhandledDropCallback(true);
mToken = null;
try {
@@ -632,7 +634,8 @@ public class DragDropControllerTests extends WindowTestsBase {
startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
// Trigger an unhandled drop and verify the global drag listener was not called
mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, mDisplayContent.getDisplayId(),
+ invalidXY, invalidXY);
mToken = null;
try {
verify(listener, never()).onUnhandledDrop(any(), any());
@@ -654,7 +657,8 @@ public class DragDropControllerTests extends WindowTestsBase {
ClipData.newPlainText("label", "Test"), () -> {
// Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
- mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */,
+ mDisplayContent.getDisplayId(), invalidXY, invalidXY);
// Verify that the unhandled drop listener callback timeout has been scheduled
final Handler handler = mTarget.getHandler();
@@ -673,7 +677,8 @@ public class DragDropControllerTests extends WindowTestsBase {
private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
startDrag(flags, data, () -> {
mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
- mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY);
+ mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), dropX,
+ dropY);
mToken = mWindow.mClient.asBinder();
});
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 201ff51f1495..6a738ae54dcd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -391,7 +391,6 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
@DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
public void testRepositionLandscapeImmersiveAppWithDisplayCutout() {
final int dw = 2100;
@@ -3783,7 +3782,6 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING)
public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 0cd036f0c61c..19c1ce2616af 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -741,16 +741,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allowed because TaskFragments are not organized by the caller organizer.
assertApplyTransactionDisallowed(mTransaction);
- assertNull(mTaskFragment.getAdjacentTaskFragment());
- assertNull(taskFragment2.getAdjacentTaskFragment());
+ assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+ assertFalse(taskFragment2.hasAdjacentTaskFragment());
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
"Test:TaskFragmentOrganizer" /* processName */);
// Not allowed because TaskFragment2 is not organized by the caller organizer.
assertApplyTransactionDisallowed(mTransaction);
- assertNull(mTaskFragment.getAdjacentTaskFragment());
- assertNull(taskFragment2.getAdjacentTaskFragment());
+ assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+ assertFalse(taskFragment2.hasAdjacentTaskFragment());
mTaskFragment.onTaskFragmentOrganizerRemoved();
taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
@@ -758,14 +758,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allowed because mTaskFragment is not organized by the caller organizer.
assertApplyTransactionDisallowed(mTransaction);
- assertNull(mTaskFragment.getAdjacentTaskFragment());
- assertNull(taskFragment2.getAdjacentTaskFragment());
+ assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+ assertFalse(taskFragment2.hasAdjacentTaskFragment());
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
"Test:TaskFragmentOrganizer" /* processName */);
assertApplyTransactionAllowed(mTransaction);
- assertEquals(taskFragment2, mTaskFragment.getAdjacentTaskFragment());
+ assertTrue(mTaskFragment.isAdjacentTo(taskFragment2));
}
@Test
@@ -790,14 +790,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Not allowed because TaskFragment is not organized by the caller organizer.
assertApplyTransactionDisallowed(mTransaction);
- assertEquals(taskFragment2, mTaskFragment.getAdjacentTaskFragment());
+ assertTrue(mTaskFragment.isAdjacentTo(taskFragment2));
mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
"Test:TaskFragmentOrganizer" /* processName */);
assertApplyTransactionAllowed(mTransaction);
- assertNull(mTaskFragment.getAdjacentTaskFragment());
- assertNull(taskFragment2.getAdjacentTaskFragment());
+ assertFalse(mTaskFragment.hasAdjacentTaskFragment());
+ assertFalse(taskFragment2.hasAdjacentTaskFragment());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index dafa96f91812..35a2546fca1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -365,7 +365,7 @@ public class TaskFragmentTest extends WindowTestsBase {
assertEquals(taskFragmentBounds, activity.getBounds());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
- assertEquals(taskFragment1, taskFragment0.getAdjacentTaskFragment());
+ assertTrue(taskFragment0.isAdjacentTo(taskFragment1));
assertEquals(taskFragment1, taskFragment0.getCompanionTaskFragment());
assertNotEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
@@ -381,7 +381,7 @@ public class TaskFragmentTest extends WindowTestsBase {
assertEquals(taskBounds, taskFragment0.getBounds());
assertEquals(taskBounds, activity.getBounds());
assertEquals(Configuration.EMPTY, taskFragment0.getRequestedOverrideConfiguration());
- assertNull(taskFragment0.getAdjacentTaskFragment());
+ assertFalse(taskFragment0.hasAdjacentTaskFragment());
assertNull(taskFragment0.getCompanionTaskFragment());
assertEquals(TaskFragmentAnimationParams.DEFAULT, taskFragment0.getAnimationParams());
// Because the whole Task is entering PiP, no need to record for future reparent.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 6655932b060b..c6b2a6b8d42f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,12 +46,15 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.window.TaskSnapshot;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+
import com.google.android.collect.Sets;
import org.junit.Test;
@@ -285,4 +289,27 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
assertFalse(success);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_EXCLUDE_DRAWING_APP_THEME_SNAPSHOT_FROM_LOCK)
+ public void testRecordTaskSnapshot() {
+ spyOn(mWm.mTaskSnapshotController.mCache);
+ spyOn(mWm.mTaskSnapshotController);
+ doReturn(false).when(mWm.mTaskSnapshotController).shouldDisableSnapshots();
+
+ final WindowState normalWindow = createWindow(null,
+ FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow");
+ final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
+ .setTopActivityComponent(normalWindow.mActivityRecord.mActivityComponent).build();
+ doReturn(snapshot).when(mWm.mTaskSnapshotController).snapshot(any());
+ final Task task = normalWindow.mActivityRecord.getTask();
+ mWm.mTaskSnapshotController.recordSnapshot(task);
+ verify(mWm.mTaskSnapshotController.mCache).putSnapshot(eq(task), any());
+ clearInvocations(mWm.mTaskSnapshotController.mCache);
+
+ normalWindow.mAttrs.flags |= FLAG_SECURE;
+ mWm.mTaskSnapshotController.recordSnapshot(task);
+ waitHandlerIdle(mWm.mH);
+ verify(mWm.mTaskSnapshotController.mCache).putSnapshot(eq(task), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index da4c522834a6..1281be5132d3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -909,8 +909,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setAdjacentRoots(info1.token, info2.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
- assertEquals(task1.getAdjacentTaskFragment(), task2);
- assertEquals(task2.getAdjacentTaskFragment(), task1);
+ assertTrue(task1.isAdjacentTo(task2));
+ assertTrue(task2.isAdjacentTo(task1));
wct = new WindowContainerTransaction();
wct.setLaunchAdjacentFlagRoot(info1.token);
@@ -921,8 +921,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
wct.clearAdjacentRoots(info1.token);
wct.clearLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
- assertEquals(task1.getAdjacentTaskFragment(), null);
- assertEquals(task2.getAdjacentTaskFragment(), null);
+ assertFalse(task1.hasAdjacentTaskFragment());
+ assertFalse(task2.hasAdjacentTaskFragment());
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index 34f0c191ecf5..fe9f63615757 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -76,6 +76,7 @@ public class IntegrationTests {
private ActivityTestRule<EmptyActivity> mEmptyActivityRule =
new ActivityTestRule<>(EmptyActivity.class, false , true);
+
@Before
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -163,7 +164,7 @@ public class IntegrationTests {
// of that state.
for (int i = 0; i < uiStates.size(); i++) {
StateTracker.StateData stateData = uiStates.get(i);
- if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) {
+ if (stateData.mWidgetCategory.equals(AppJankStats.WIDGET_CATEGORY_ANIMATION)) {
assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd);
}
}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
index 30c568be7716..c90595782cd1 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -215,7 +215,8 @@ public class JankDataProcessorTest {
assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames());
assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames());
- int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters();
+ int[] originalHistogramBuckets =
+ jankStats.getRelativeFrameTimeHistogram().getBucketCounters();
int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets();
for (int i = 0; i < frameOverrunBuckets.length; i++) {
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
index 0b4d97ed20d6..df92898d76b1 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
@@ -17,7 +17,7 @@
package android.app.jank.tests;
import android.app.jank.AppJankStats;
-import android.app.jank.FrameOverrunHistogram;
+import android.app.jank.RelativeFrameTimeHistogram;
public class JankUtils {
private static final int APP_ID = 25;
@@ -29,8 +29,8 @@ public class JankUtils {
AppJankStats jankStats = new AppJankStats(
/*App Uid*/APP_ID,
/*Widget Id*/"test widget id",
- /*Widget Category*/AppJankStats.SCROLL,
- /*Widget State*/AppJankStats.SCROLLING,
+ /*Widget Category*/AppJankStats.WIDGET_CATEGORY_SCROLL,
+ /*Widget State*/AppJankStats.WIDGET_STATE_SCROLLING,
/*Total Frames*/100,
/*Janky Frames*/25,
getOverrunHistogram()
@@ -41,12 +41,12 @@ public class JankUtils {
/**
* Returns a mock histogram to be used with an AppJankStats object.
*/
- public static FrameOverrunHistogram getOverrunHistogram() {
- FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram();
- overrunHistogram.addFrameOverrunMillis(-2);
- overrunHistogram.addFrameOverrunMillis(1);
- overrunHistogram.addFrameOverrunMillis(5);
- overrunHistogram.addFrameOverrunMillis(25);
+ public static RelativeFrameTimeHistogram getOverrunHistogram() {
+ RelativeFrameTimeHistogram overrunHistogram = new RelativeFrameTimeHistogram();
+ overrunHistogram.addRelativeFrameTimeMillis(-2);
+ overrunHistogram.addRelativeFrameTimeMillis(1);
+ overrunHistogram.addRelativeFrameTimeMillis(5);
+ overrunHistogram.addRelativeFrameTimeMillis(25);
return overrunHistogram;
}
}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
index 5fff46038ead..71796d64ddee 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java
@@ -45,8 +45,8 @@ public class TestWidget extends View {
*/
public void simulateAnimationStarting() {
if (jankTrackerCreated()) {
- mJankTracker.addUiState(AppJankStats.ANIMATION,
- Integer.toString(this.getId()), AppJankStats.ANIMATING);
+ mJankTracker.addUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION,
+ Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING);
}
}
@@ -55,8 +55,8 @@ public class TestWidget extends View {
*/
public void simulateAnimationEnding() {
if (jankTrackerCreated()) {
- mJankTracker.removeUiState(AppJankStats.ANIMATION,
- Integer.toString(this.getId()), AppJankStats.ANIMATING);
+ mJankTracker.removeUiState(AppJankStats.WIDGET_CATEGORY_ANIMATION,
+ Integer.toString(this.getId()), AppJankStats.WIDGET_STATE_ANIMATING);
}
}
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 700856c50bae..14c8de8db5fc 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -819,7 +819,7 @@ public class GraphicsActivity extends Activity {
private List<Float> getExpectedFrameRateForCompatibility(int compatibility) {
assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
+ compatibility,
- compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
+ compatibility == Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST);
Display display = getDisplay();
List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 4d4827676c74..f1d4dc6b8faf 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -85,7 +85,8 @@ public class SurfaceControlTest {
@Test
public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException {
GraphicsActivity activity = mActivityRule.getActivity();
- activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE);
+ activity.testSurfaceControlFrameRateCompatibility(
+ Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST);
}
@Test
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 2e7b20763b9e..2db8b1e18ec8 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -29,6 +29,7 @@ import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
+import android.view.KeyEvent.KEYCODE_MINUS
import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
import android.view.KeyEvent.META_META_ON
import android.view.WindowInsets
@@ -160,10 +161,21 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
}
- fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) {
- val caption = getCaptionForTheApp(wmHelper, device)
- val minimizeButton = getMinimizeButtonForTheApp(caption)
- minimizeButton.click()
+ fun minimizeDesktopApp(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ isPip: Boolean = false,
+ usingKeyboard: Boolean = false,
+ ) {
+ if (usingKeyboard) {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_MINUS, META_META_ON)
+ } else {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val minimizeButton = getMinimizeButtonForTheApp(caption)
+ minimizeButton.click()
+ }
+
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
@@ -226,8 +238,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
toLeft: Boolean,
) {
val bracketKey = if (toLeft) KEYCODE_LEFT_BRACKET else KEYCODE_RIGHT_BRACKET
- keyEventHelper.actionDown(bracketKey, META_META_ON)
- keyEventHelper.actionUp(bracketKey, META_META_ON)
+ keyEventHelper.press(bracketKey, META_META_ON)
waitAndVerifySnapResize(wmHelper, context, toLeft)
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
index ebd8cc3ce1b4..55ed09154aee 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
@@ -29,6 +29,10 @@ import android.view.KeyEvent
class KeyEventHelper(
private val instr: Instrumentation,
) {
+ fun press(keyCode: Int, metaState: Int = 0) {
+ actionDown(keyCode, metaState)
+ actionUp(keyCode, metaState)
+ }
fun actionDown(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) {
injectKeyEvent(ACTION_DOWN, keyCode, metaState, downTime = time, eventTime = time)
diff --git a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt
index 281837920548..860d9f680c4c 100644
--- a/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt
+++ b/tests/Input/src/com/android/test/input/KeyCharacterMapTest.kt
@@ -16,10 +16,17 @@
package com.android.test.input
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+
import android.view.KeyCharacterMap
import android.view.KeyEvent
+import com.android.hardware.input.Flags
+
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Rule
import org.junit.Test
/**
@@ -30,26 +37,38 @@ import org.junit.Test
*
*/
class KeyCharacterMapTest {
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
@Test
+ @EnableFlags(Flags.FLAG_REMOVE_FALLBACK_MODIFIERS)
fun testGetFallback() {
// Based off of VIRTUAL kcm fallbacks.
val keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD)
// One modifier fallback.
- assertEquals(
- keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE,
- KeyEvent.META_CTRL_ON).keyCode,
- KeyEvent.KEYCODE_LANGUAGE_SWITCH)
+ val oneModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON)
+ assertEquals(KeyEvent.KEYCODE_LANGUAGE_SWITCH, oneModifierFallback.keyCode)
+ assertEquals(0, oneModifierFallback.metaState)
// Multiple modifier fallback.
- assertEquals(
- keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL,
- KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON).keyCode,
- KeyEvent.KEYCODE_BACK)
+ val twoModifierFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON)
+ assertEquals(KeyEvent.KEYCODE_BACK, twoModifierFallback.keyCode)
+ assertEquals(0, twoModifierFallback.metaState)
// No default button, fallback only.
- assertEquals(
- keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0).keyCode,
- KeyEvent.KEYCODE_DPAD_CENTER)
+ val keyOnlyFallback =
+ keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_BUTTON_A, 0)
+ assertEquals(KeyEvent.KEYCODE_DPAD_CENTER, keyOnlyFallback.keyCode)
+ assertEquals(0, keyOnlyFallback.metaState)
+
+ // A key event that is not an exact match for a fallback. Expect a null return.
+ // E.g. Ctrl + Space -> LanguageSwitch
+ // Ctrl + Alt + Space -> Ctrl + Alt + Space (No fallback).
+ val noMatchFallback = keyCharacterMap.getFallbackAction(KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON)
+ assertNull(noMatchFallback)
}
}
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 5e0f87f0dcaf..60c4bf5c4131 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -292,13 +292,12 @@ bool processFile(Bundle* bundle, ZipFile* zip,
}
if (!hasData) {
const String8& srcName = file->getSourceFile();
- time_t fileModWhen;
- fileModWhen = getFileModDate(srcName.c_str());
- if (fileModWhen == (time_t) -1) { // file existence tested earlier,
- return false; // not expecting an error here
+ auto fileModWhen = getFileModDate(srcName.c_str());
+ if (fileModWhen == kInvalidModDate) { // file existence tested earlier,
+ return false; // not expecting an error here
}
-
- if (fileModWhen > entry->getModWhen()) {
+
+ if (toTimeT(fileModWhen) > entry->getModWhen()) {
// mark as deleted so add() will succeed
if (bundle->getVerbose()) {
printf(" (removing old '%s')\n", storageName.c_str());
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 661df4d0fe33..e24fe07f959b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -683,8 +683,6 @@ class ChunkPrinter {
item->PrettyPrint(printer_);
printer_->Print(")");
}
-
- printer_->Print("\n");
}
void PrintQualifiers(uint32_t qualifiers) const {
@@ -763,11 +761,13 @@ class ChunkPrinter {
bool PrintTableType(const ResTable_type* chunk) {
printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id)));
- printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1)
- .c_str()));
+ const auto name =
+ android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1);
+ printer_->Print(StringPrintf(" name: %s", name.c_str()));
printer_->Print(StringPrintf(" flags: 0x%02x", android::util::DeviceToHost32(chunk->flags)));
+ printer_->Print(android::util::DeviceToHost32(chunk->flags) & ResTable_type::FLAG_SPARSE
+ ? " (SPARSE)"
+ : " (DENSE)");
printer_->Print(
StringPrintf(" entryCount: %u", android::util::DeviceToHost32(chunk->entryCount)));
printer_->Print(
@@ -777,8 +777,7 @@ class ChunkPrinter {
config.copyFromDtoH(chunk->config);
printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str()));
- const ResourceType* type = ParseResourceType(
- android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1));
+ const ResourceType* type = ParseResourceType(name);
printer_->Indent();
@@ -817,11 +816,8 @@ class ChunkPrinter {
for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
- printer_->Print(StringPrintf(
- " name: %s name-id:%d\n",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(maps[i].name.ident))
- .c_str(),
- android::util::DeviceToHost32(maps[i].name.ident)));
+ printer_->Print(StringPrintf(" name-id: 0x%08x\n",
+ android::util::DeviceToHost32(maps[i].name.ident)));
}
} else {
printer_->Print("\n");
@@ -829,6 +825,8 @@ class ChunkPrinter {
// Print the value of the entry
Res_value value = entry->value();
PrintResValue(&value, config, type);
+
+ printer_->Print("\n");
}
printer_->Undent();