summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp17
-rw-r--r--Android.mk20
-rw-r--r--Ravenwood.bp2
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java38
-rw-r--r--apex/jobscheduler/OWNERS1
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/OWNERS1
-rw-r--r--apex/jobscheduler/service/aconfig/Android.bp3
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig1
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig1
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java91
-rw-r--r--api/Android.bp12
-rw-r--r--api/StubLibraries.bp123
-rw-r--r--api/coverage/tools/ExtractFlaggedApis.kt84
-rw-r--r--cmds/incident_helper/OWNERS1
-rw-r--r--cmds/incidentd/src/PrivacyFilter.cpp4
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/api/test-current.txt11
-rw-r--r--core/java/android/app/ActivityManager.java9
-rw-r--r--core/java/android/app/ActivityOptions.java13
-rw-r--r--core/java/android/app/ActivityThread.java15
-rw-r--r--core/java/android/app/AppOpsManager.java22
-rw-r--r--core/java/android/app/AppOpsManagerInternal.java18
-rw-r--r--core/java/android/app/ContextImpl.java40
-rw-r--r--core/java/android/app/IActivityManager.aidl47
-rw-r--r--core/java/android/app/IApplicationThread.aidl3
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl4
-rw-r--r--core/java/android/app/Notification.java7
-rw-r--r--core/java/android/app/UiAutomation.java76
-rw-r--r--core/java/android/app/UiAutomationConnection.java65
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java35
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig14
-rw-r--r--core/java/android/app/assist/AssistStructure.java13
-rw-r--r--core/java/android/app/wearable/IWearableSensingCallback.aidl35
-rw-r--r--core/java/android/app/wearable/IWearableSensingManager.aidl5
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java106
-rw-r--r--core/java/android/companion/ObservingDevicePresenceRequest.java6
-rw-r--r--core/java/android/content/Intent.java8
-rw-r--r--core/java/android/content/pm/UserProperties.java2
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java22
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java11
-rw-r--r--core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java30
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java17
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java5
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java29
-rw-r--r--core/java/android/hardware/devicestate/DeviceState.java27
-rw-r--r--core/java/android/hardware/devicestate/OWNERS1
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl25
-rw-r--r--core/java/android/hardware/input/InputManager.java125
-rw-r--r--core/java/android/hardware/input/InputManagerGlobal.java25
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java59
-rw-r--r--core/java/android/os/RecoverySystem.java4
-rw-r--r--core/java/android/permission/PermissionManager.java10
-rw-r--r--core/java/android/permission/TEST_MAPPING24
-rw-r--r--core/java/android/permission/flags.aconfig10
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/dreams/DreamService.java53
-rw-r--r--core/java/android/service/dreams/flags.aconfig11
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java16
-rw-r--r--core/java/android/service/wearable/IWearableSensingService.aidl5
-rw-r--r--core/java/android/service/wearable/WearableSensingService.java90
-rw-r--r--core/java/android/text/flags/flags.aconfig24
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java4
-rw-r--r--core/java/android/view/InsetsController.java57
-rw-r--r--core/java/android/view/MotionEvent.java7
-rw-r--r--core/java/android/view/PendingInsetsController.java20
-rw-r--r--core/java/android/view/Surface.java4
-rw-r--r--core/java/android/view/SurfaceControl.java2
-rw-r--r--core/java/android/view/View.java90
-rw-r--r--core/java/android/view/ViewRootImpl.java86
-rw-r--r--core/java/android/view/ViewRootInsetsControllerHost.java17
-rw-r--r--core/java/android/view/WindowInsetsController.java21
-rw-r--r--core/java/android/view/WindowManager.java24
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java26
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java3
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig12
-rw-r--r--core/java/android/widget/ProgressBar.java9
-rw-r--r--core/java/android/widget/ScrollView.java2
-rw-r--r--core/java/android/window/flags/accessibility.aconfig12
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig14
-rw-r--r--core/java/android/window/flags/wallpaper_manager.aconfig10
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig29
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java39
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java19
-rw-r--r--core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java34
-rw-r--r--core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java34
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java10
-rw-r--r--core/java/com/android/internal/compat/Android.bp1
-rw-r--r--core/java/com/android/internal/compat/compat_logging_flags.aconfig3
-rw-r--r--core/java/com/android/internal/foldables/Android.bp1
-rw-r--r--core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig1
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java30
-rw-r--r--core/java/com/android/internal/protolog/LegacyProtoLogImpl.java19
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java104
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogDataSource.java31
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java20
-rw-r--r--core/java/com/android/internal/protolog/common/IProtoLog.java8
-rw-r--r--core/java/com/android/internal/protolog/common/IProtoLogGroup.java2
-rw-r--r--core/java/com/android/internal/protolog/common/ProtoLog.java2
-rw-r--r--core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java6
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/OWNERS3
-rw-r--r--core/jni/android_os_Debug.cpp51
-rw-r--r--core/jni/android_text_Hyphenator.cpp22
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.cpp2
-rw-r--r--core/proto/OWNERS1
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt2
-rw-r--r--core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java126
-rw-r--r--core/tests/coretests/src/android/view/MotionEventTest.java105
-rw-r--r--core/tests/coretests/src/android/view/PendingInsetsControllerTest.java47
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java36
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java102
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java42
-rw-r--r--data/etc/core.protolog.pbbin54086 -> 54168 bytes
-rw-r--r--data/etc/services.core.protolog.json18
-rw-r--r--data/keyboards/Generic.kl2
-rw-r--r--graphics/java/Android.bp1
-rw-r--r--graphics/java/android/framework_graphics.aconfig3
-rw-r--r--graphics/java/android/graphics/Bitmap.java91
-rw-r--r--keystore/java/android/security/KeyStore.java14
-rw-r--r--keystore/java/android/security/KeyStoreAuthorization.java (renamed from keystore/java/android/security/Authorization.java)29
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/aconfig/Android.bp1
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig1
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml39
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl (renamed from tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java)13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java311
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java188
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java719
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java538
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java1081
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java427
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java182
-rw-r--r--libs/androidfw/ZipFileRO.cpp2
-rw-r--r--libs/dream/lowlight/tests/Android.bp2
-rw-r--r--libs/hwui/Android.bp9
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig1
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp132
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.h77
-rw-r--r--libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp194
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp16
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp229
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h24
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp13
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h59
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h (renamed from libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h)4
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h (renamed from libs/hwui/pipeline/skia/SkiaVulkanPipeline.h)4
l---------libs/hwui/platform/host/android/api-level.h1
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h83
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h35
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h35
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp4
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h3
-rw-r--r--media/java/android/media/FadeManagerConfiguration.java16
-rw-r--r--media/java/android/media/MediaCodec.java25
-rw-r--r--media/java/android/media/MediaDescription.java10
-rw-r--r--media/java/android/media/MediaRouter2.java156
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig27
-rw-r--r--media/java/android/media/flags/performance.aconfig11
-rw-r--r--media/java/android/media/session/ISessionManager.aidl3
-rw-r--r--media/jni/android_media_MediaCodec.cpp26
-rw-r--r--media/jni/android_media_MediaCodec.h2
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java42
-rw-r--r--native/android/performance_hint.cpp25
-rw-r--r--native/android/surface_control.cpp4
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java82
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/RescueParty.java6
-rw-r--r--packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java (renamed from packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java)2
-rw-r--r--packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java (renamed from packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java)2
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java115
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/FileUtils.java128
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java188
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java118
-rw-r--r--packages/CredentialManager/res/values/strings.xml7
-rw-r--r--packages/CredentialManager/shared/project.config9
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt30
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt21
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt35
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt122
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt (renamed from packages/SystemUI/src-debug/com/android/systemui/util/Compile.java)13
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt67
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt100
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt1
-rw-r--r--packages/EasterEgg/Android.bp1
-rw-r--r--packages/EasterEgg/easter_egg_flags.aconfig1
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_french.kcm80
-rw-r--r--packages/PackageInstaller/Android.bp3
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml11
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java173
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java59
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java36
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java327
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java100
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java202
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java621
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java133
-rw-r--r--packages/SettingsLib/Android.bp4
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml31
-rw-r--r--packages/SettingsLib/Spa/spa/res/values/themes.xml5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaDialogWindowTypeActivity.kt78
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt309
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt6
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig1
-rw-r--r--packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java311
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java44
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java42
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java152
-rw-r--r--packages/SettingsProvider/Android.bp2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java44
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig1
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java19
-rw-r--r--packages/SystemUI/Android.bp22
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig1
-rw-r--r--packages/SystemUI/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig1
-rw-r--r--packages/SystemUI/aconfig/biometrics_framework.aconfig3
-rw-r--r--packages/SystemUI/aconfig/communal.aconfig1
-rw-r--r--packages/SystemUI/aconfig/cross_device_control.aconfig1
-rw-r--r--packages/SystemUI/aconfig/predictive_back.aconfig3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig23
-rw-r--r--packages/SystemUI/animation/Android.bp3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt252
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt60
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt46
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt32
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt121
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt76
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt24
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt39
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt110
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt61
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt43
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt78
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt14
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt23
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt136
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt249
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt146
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt167
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt166
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt88
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt168
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt161
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt96
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt53
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt146
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml1
-rw-r--r--packages/SystemUI/res/anim/slide_in_up.xml21
-rw-r--r--packages/SystemUI/res/anim/slide_out_down.xml21
-rw-r--r--packages/SystemUI/res/color/menu_item_text.xml24
-rw-r--r--packages/SystemUI/res/drawable/ic_bugreport.xml32
-rw-r--r--packages/SystemUI/res/drawable/ic_finder_active.xml4
-rw-r--r--packages/SystemUI/res/drawable/ic_shortcutlist_search.xml7
-rw-r--r--packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/shortcut_button_colored.xml2
-rw-r--r--packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml2
-rw-r--r--packages/SystemUI/res/drawable/shortcut_dialog_bg.xml6
-rw-r--r--packages/SystemUI/res/drawable/shortcut_search_background.xml6
-rw-r--r--packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml2
-rw-r--r--packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml34
-rw-r--r--packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml37
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_button_bar.xml11
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml33
-rw-r--r--packages/SystemUI/res/layout/clipboard_edit_text_activity.xml1
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml6
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml4
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml4
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml41
-rw-r--r--packages/SystemUI/res/layout/record_issue_dialog.xml41
-rw-r--r--packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml4
-rw-r--r--packages/SystemUI/res/layout/screenshot_shelf.xml10
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml3
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml28
-rw-r--r--packages/SystemUI/res/values/strings.xml16
-rw-r--r--packages/SystemUI/res/values/styles.xml25
-rw-r--r--packages/SystemUI/shared/Android.bp4
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java95
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl7
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt342
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt186
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt127
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java (renamed from packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java)100
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt353
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt (renamed from packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt)24
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt (renamed from packages/SystemUI/src-release/com/android/systemui/util/Compile.java)15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt186
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java343
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt285
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt165
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt)44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt (renamed from packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java81
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/tests/AndroidTest.xml9
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json393
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json393
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json393
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json393
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt193
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt)38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java)52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt139
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt)19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt164
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt200
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java81
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt89
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastSenderKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt38
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt (renamed from packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml)20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt)4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt33
-rw-r--r--packages/overlays/Android.bp3
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml22
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml22
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml22
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml27
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml22
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml62
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml30
-rw-r--r--packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml22
-rw-r--r--ravenwood/TEST_MAPPING11
-rw-r--r--ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp2
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java47
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java39
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java43
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java (renamed from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java)2
-rw-r--r--ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java (renamed from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java)2
-rw-r--r--ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java (renamed from ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java)4
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java6
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java26
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java35
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java)2
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java)2
-rw-r--r--ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java (renamed from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java)2
-rw-r--r--ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java (renamed from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java)2
-rw-r--r--ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java (renamed from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java)2
-rw-r--r--services/Android.bp35
-rw-r--r--services/accessibility/Android.bp1
-rw-r--r--services/accessibility/accessibility.aconfig1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java22
-rw-r--r--services/backup/Android.bp1
-rw-r--r--services/backup/flags.aconfig3
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java28
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java4
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java4
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java)144
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java)94
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java)64
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java (renamed from services/companion/java/com/android/server/companion/presence/ObservableUuid.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java (renamed from services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java)2
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java6
-rw-r--r--services/companion/java/com/android/server/companion/virtual/Android.bp1
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java20
-rw-r--r--services/companion/java/com/android/server/companion/virtual/flags.aconfig1
-rw-r--r--services/core/Android.bp3
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java106
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING15
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/AccessCheckDelegateHelper.java272
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java468
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java20
-rw-r--r--services/core/java/com/android/server/am/Android.bp1
-rw-r--r--services/core/java/com/android/server/am/AppBatteryTracker.java2
-rw-r--r--services/core/java/com/android/server/am/AppPermissionTracker.java2
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java4
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java10
-rw-r--r--services/core/java/com/android/server/am/BaseAppStateTracker.java8
-rw-r--r--services/core/java/com/android/server/am/OWNERS2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java40
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java13
-rw-r--r--services/core/java/com/android/server/am/UserController.java93
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java16
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java34
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java21
-rw-r--r--services/core/java/com/android/server/audio/AudioManagerShellCommand.java62
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java48
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java10
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java75
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java76
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java15
-rw-r--r--services/core/java/com/android/server/biometrics/Android.bp1
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java18
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java37
-rw-r--r--services/core/java/com/android/server/biometrics/biometrics.aconfig1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java2
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java14
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java36
-rw-r--r--services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java2
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/Convert.java13
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/Tuner.java15
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java17
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java16
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java44
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/Convert.java76
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java6
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java43
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java31
-rw-r--r--services/core/java/com/android/server/connectivity/Android.bp10
-rw-r--r--services/core/java/com/android/server/connectivity/flags.aconfig8
-rw-r--r--services/core/java/com/android/server/devicestate/OWNERS3
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java13
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java703
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java92
-rw-r--r--services/core/java/com/android/server/display/HysteresisLevels.java152
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java7
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java15
-rw-r--r--services/core/java/com/android/server/display/NormalBrightnessModeController.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java21
-rw-r--r--services/core/java/com/android/server/display/config/HysteresisLevels.java463
-rw-r--r--services/core/java/com/android/server/display/feature/Android.bp1
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/feature/Android.bp1
-rw-r--r--services/core/java/com/android/server/feature/dropbox_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/graphics/fonts/FontManagerService.java6
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java61
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java48
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java430
-rw-r--r--services/core/java/com/android/server/input/PersistentDataStore.java185
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java10
-rw-r--r--services/core/java/com/android/server/media/AudioManagerRouteController.java59
-rw-r--r--services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java75
-rw-r--r--services/core/java/com/android/server/media/MediaKeyDispatcher.java1
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java44
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java22
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java5
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java126
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java145
-rw-r--r--services/core/java/com/android/server/media/MediaShellCommand.java6
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java11
-rw-r--r--services/core/java/com/android/server/media/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/net/Android.bp1
-rw-r--r--services/core/java/com/android/server/net/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/notification/Android.bp1
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java3
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java96
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/os/Android.bp1
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java10
-rw-r--r--services/core/java/com/android/server/os/core_os_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java34
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/NoFilteringResolver.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java22
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java17
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java15
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java3
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java499
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java183
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java58
-rw-r--r--services/core/java/com/android/server/policy/Android.bp1
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java20
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java9
-rw-r--r--services/core/java/com/android/server/policy/window_policy_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/power/Android.bp1
-rw-r--r--services/core/java/com/android/server/power/batterysaver/OWNERS3
-rw-r--r--services/core/java/com/android/server/power/feature/Android.bp1
-rw-r--r--services/core/java/com/android/server/power/feature/power_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/power/hint/Android.bp1
-rw-r--r--services/core/java/com/android/server/power/hint/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/rollback/README.md13
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java4
-rw-r--r--services/core/java/com/android/server/stats/Android.bp1
-rw-r--r--services/core/java/com/android/server/stats/stats_flags.aconfig1
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java28
-rw-r--r--services/core/java/com/android/server/utils/Android.bp1
-rw-r--r--services/core/java/com/android/server/utils/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/utils/quota/OWNERS1
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java42
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorControlService.java11
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java151
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperData.java6
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java26
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java122
-rw-r--r--services/core/java/com/android/server/wearable/RemoteWearableSensingService.java68
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java71
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java54
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java6
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java140
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java6
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java28
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java12
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java76
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java25
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java14
-rw-r--r--services/core/java/com/android/server/wm/DisplayUpdater.java13
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java12
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java3
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java25
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java7
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java125
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp1
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig1
-rw-r--r--services/java/com/android/server/SystemServer.java268
-rw-r--r--services/permission/TEST_MAPPING22
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt13
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt3
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt44
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java146
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java24
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java35
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java50
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java35
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java25
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java6
-rw-r--r--services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/OWNERS2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java126
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java50
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java54
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java2
-rw-r--r--services/tests/selinux/Android.bp60
-rw-r--r--services/tests/selinux/AndroidManifest.xml28
-rw-r--r--services/tests/selinux/AndroidTest.xml33
-rw-r--r--services/tests/selinux/OWNERS (renamed from services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS)0
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/RateLimiterTest.java (renamed from services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java)0
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java (renamed from services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java)0
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java (renamed from services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java)3
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING17
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING17
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING20
-rw-r--r--services/tests/servicestests/src/com/android/server/om/TEST_MAPPING13
-rw-r--r--services/tests/servicestests/src/com/android/server/os/TEST_MAPPING11
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING26
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java148
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java94
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java74
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java29
-rw-r--r--services/usb/Android.bp1
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java60
-rw-r--r--services/usb/java/com/android/server/usb/flags/usb_flags.aconfig1
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java2
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java32
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java63
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java2
-rw-r--r--telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl6
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java18
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java13
-rw-r--r--telephony/java/android/telephony/satellite/stub/ISatellite.aidl5
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java9
-rw-r--r--tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt46
-rw-r--r--tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt4
-rw-r--r--tests/FlickerTests/README.md75
-rw-r--r--tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt1067
-rw-r--r--tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java2
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java4
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java147
-rw-r--r--tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java116
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java69
-rw-r--r--tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java24
-rw-r--r--tests/UsbManagerTests/Android.bp1
-rw-r--r--tests/UsbTests/Android.bp3
-rw-r--r--tests/UsbTests/TEST_MAPPING7
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java174
-rw-r--r--tools/app_metadata_bundles/Android.bp3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java156
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java)3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java)33
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java)2
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java)2
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java)6
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java)18
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java)18
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java)26
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java)3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java)7
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java)3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java)4
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java)3
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java)5
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java197
-rw-r--r--tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java (renamed from tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java)35
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java35
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java (renamed from tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java)47
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java97
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java107
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java216
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java102
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java100
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java77
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java75
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java75
-rw-r--r--tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java152
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml3
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml3
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml14
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml25
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml14
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml31
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml5
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml27
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml17
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml22
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml12
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml12
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml13
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml50
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml12
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml6
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml7
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml12
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml13
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml13
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml8
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml9
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml2
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml9
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml3
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml16
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml3
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml4
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml1
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml26
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml11
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml (renamed from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml)0
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml (renamed from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml)0
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml (renamed from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml)0
-rw-r--r--tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml (renamed from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml)0
-rw-r--r--tools/hoststubgen/hoststubgen/Android.bp23
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt54
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt (renamed from tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt)12
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt2
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt5
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt11
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt113
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt24
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt46
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt56
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java2
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java11
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt72
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt16
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt14
1197 files changed, 31550 insertions, 12872 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 65feadbf23d0..1233fa1ab773 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -83,6 +83,7 @@ aconfig_declarations_group {
"com.android.internal.pm.pkg.component.flags-aconfig-java",
"com.android.media.flags.bettertogether-aconfig-java",
"com.android.media.flags.editing-aconfig-java",
+ "com.android.media.flags.performance-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
"com.android.net.thread.platform.flags-aconfig-java",
"com.android.server.flags.services-aconfig-java",
@@ -347,6 +348,7 @@ java_aconfig_library {
apex_available: [
"//apex_available:platform",
"com.android.mediaprovider",
+ "com.android.permission",
],
}
@@ -594,6 +596,21 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Performance
+aconfig_declarations {
+ name: "com.android.media.flags.performance-aconfig",
+ package: "com.android.media.performance.flags",
+ srcs: [
+ "media/java/android/media/flags/performance.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com.android.media.flags.performance-aconfig-java",
+ aconfig_declarations: "com.android.media.flags.performance-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media TV
aconfig_declarations {
name: "android.media.tv.flags-aconfig",
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index a126c52e2491..000000000000
--- a/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-
-# TODO: Empty this file after all subdirectories' Android.mk have been
-# converted to Android.bp to avoid using any newly added Android.mk.
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/Ravenwood.bp b/Ravenwood.bp
index c3b22c4f4331..7c7c0e298f3f 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -168,6 +168,7 @@ java_library {
"services.core.ravenwood",
],
jarjar_rules: ":ravenwood-services-jarjar-rules",
+ visibility: ["//visibility:private"],
}
java_library {
@@ -179,6 +180,7 @@ java_library {
"services.core.ravenwood",
],
jarjar_rules: ":ravenwood-services-jarjar-rules",
+ visibility: ["//visibility:private"],
}
java_library {
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 68717623d05d..762e2af09cd3 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -366,7 +366,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -429,7 +429,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -545,7 +545,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(currentUserId);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
mRunner.resumeTimingForNextIteration();
}
@@ -571,7 +571,7 @@ public class UserLifecycleTests {
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(startUser);
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
mRunner.resumeTimingForNextIteration();
}
@@ -660,7 +660,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -685,7 +685,7 @@ public class UserLifecycleTests {
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -883,7 +883,7 @@ public class UserLifecycleTests {
final int userId = createManagedProfile();
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
startUserInBackgroundAndWaitForUnlock(userId);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -905,7 +905,7 @@ public class UserLifecycleTests {
startUserInBackgroundAndWaitForUnlock(userId);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -987,7 +987,7 @@ public class UserLifecycleTests {
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -1019,7 +1019,7 @@ public class UserLifecycleTests {
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1144,7 +1144,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -1168,7 +1168,7 @@ public class UserLifecycleTests {
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -1294,15 +1294,15 @@ public class UserLifecycleTests {
* Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
* mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
*/
- private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+ private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
waitForBroadcastIdle();
- stopUser(userId, force);
+ stopUser(userId);
}
- private void stopUser(int userId, boolean force) throws RemoteException {
+ private void stopUser(int userId) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
- mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
latch.countDown();
@@ -1352,7 +1352,7 @@ public class UserLifecycleTests {
attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
if (stopNewUser) {
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
}
@@ -1471,7 +1471,7 @@ public class UserLifecycleTests {
}
private void removeUser(int userId) throws RemoteException {
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
try {
ShellHelper.runShellCommandWithTimeout("pm remove-user -w " + userId,
TIMEOUT_IN_SECOND);
@@ -1512,7 +1512,7 @@ public class UserLifecycleTests {
final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
assertTrue("Pre start was not performed for user" + userId, preStartComplete);
}
diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS
index 58434f165aa2..22b648975e5f 100644
--- a/apex/jobscheduler/OWNERS
+++ b/apex/jobscheduler/OWNERS
@@ -2,7 +2,6 @@ ctate@android.com
ctate@google.com
dplotnikov@google.com
jji@google.com
-kwekua@google.com
omakoto@google.com
suprabh@google.com
varunshah@google.com
diff --git a/apex/jobscheduler/framework/java/android/app/job/OWNERS b/apex/jobscheduler/framework/java/android/app/job/OWNERS
index b4a45f585157..0b1e559dda15 100644
--- a/apex/jobscheduler/framework/java/android/app/job/OWNERS
+++ b/apex/jobscheduler/framework/java/android/app/job/OWNERS
@@ -4,4 +4,3 @@ yamasani@google.com
omakoto@google.com
ctate@android.com
ctate@google.com
-kwekua@google.com
diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp
index 4db39dc1976b..859c67ad8910 100644
--- a/apex/jobscheduler/service/aconfig/Android.bp
+++ b/apex/jobscheduler/service/aconfig/Android.bp
@@ -2,6 +2,7 @@
aconfig_declarations {
name: "service-deviceidle.flags-aconfig",
package: "com.android.server.deviceidle",
+ container: "system",
srcs: [
"device_idle.aconfig",
],
@@ -17,6 +18,7 @@ java_aconfig_library {
aconfig_declarations {
name: "service-job.flags-aconfig",
package: "com.android.server.job",
+ container: "system",
srcs: [
"job.aconfig",
],
@@ -32,6 +34,7 @@ java_aconfig_library {
aconfig_declarations {
name: "alarm_flags",
package: "com.android.server.alarm",
+ container: "system",
srcs: ["alarm.aconfig"],
}
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index bb0f3cbd5257..d3068d7d37e8 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.alarm"
+container: "system"
flag {
name: "use_frozen_state_to_drop_listener_alarms"
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index e4cb5ad81ba0..e8c99b12828f 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.deviceidle"
+container: "system"
flag {
name: "disable_wakelocks_in_light_idle"
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5e6d3775f6a2..75e2efd2ec99 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.job"
+container: "system"
flag {
name: "batch_active_bucket_jobs"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede274bc1..096238aeda7c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1737,6 +1737,17 @@ class JobConcurrencyManager {
continue;
}
+ if (!nextPending.isReady()) {
+ // This could happen when the job count reached its quota, the constrains
+ // for the job has been updated but hasn't been removed from the pending
+ // queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b20003..cfbfa5dce399 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -512,7 +512,7 @@ public final class QuotaController extends StateController {
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_QUOTA = 0;
+ static final int MSG_REACHED_TIME_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +524,7 @@ public final class QuotaController extends StateController {
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_QUOTA = 4;
+ static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,6 +533,11 @@ public final class QuotaController extends StateController {
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
+ /**
+ * An app has reached its job count quota. The message should contain a {@link UserPackage}
+ * object.
+ */
+ static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +879,37 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
+ @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- return jobStatus.shouldTreatAsUserInitiatedJob()
+ if (jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ || isUidInForeground(jobStatus.getSourceUid())) {
+ return true;
+ }
+
+ if (standbyBucket == NEVER_INDEX) return false;
+
+ if (isQuotaFreeLocked(standbyBucket)) return true;
+
+ final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+ jobStatus.getSourcePackageName(), standbyBucket);
+ if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+ // Out of execution time quota.
+ return false;
+ }
+
+ if (mService.isCurrentlyRunningLocked(jobStatus)) {
+ // if job is running, considered as in quota so it can keep running.
+ return true;
+ }
+
+ // Check if the app is within job count quota.
+ return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
}
@GuardedBy("mLock")
@@ -909,12 +934,11 @@ public final class QuotaController extends StateController {
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+ && isUnderJobCountQuotaLocked(stats)
+ && isUnderSessionCountQuotaLocked(stats);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +947,7 @@ public final class QuotaController extends StateController {
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1472,7 @@ public final class QuotaController extends StateController {
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
+ stats.bgJobCountInWindow += count;
}
}
@@ -1683,10 +1707,11 @@ public final class QuotaController extends StateController {
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && !mService.isCurrentlyRunningLocked(js)) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
+ // individually. Running job need to determine its own quota status as well.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1805,9 +1830,8 @@ public final class QuotaController extends StateController {
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2126,6 +2150,11 @@ public final class QuotaController extends StateController {
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+ final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+ mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+ if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
+ mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+ }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2257,7 +2286,6 @@ public final class QuotaController extends StateController {
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
-
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2284,7 +2312,8 @@ public final class QuotaController extends StateController {
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+ mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2330,7 @@ public final class QuotaController extends StateController {
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2586,7 @@ public final class QuotaController extends StateController {
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping event " + event.getEventType());
+ Slog.d(TAG, "Dropping usage event " + event.getEventType());
}
break;
}
@@ -2666,7 +2695,7 @@ public final class QuotaController extends StateController {
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_QUOTA: {
+ case MSG_REACHED_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2714,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2695,7 +2724,7 @@ public final class QuotaController extends StateController {
}
break;
}
- case MSG_REACHED_EJ_QUOTA: {
+ case MSG_REACHED_EJ_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2742,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2723,6 +2752,18 @@ public final class QuotaController extends StateController {
}
break;
}
+ case MSG_REACHED_COUNT_QUOTA: {
+ UserPackage pkg = (UserPackage) msg.obj;
+ if (DEBUG) {
+ Slog.d(TAG, pkg + " has reached its count quota.");
+ }
+
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(),
+ pkg.userId, pkg.packageName));
+ break;
+ }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/api/Android.bp b/api/Android.bp
index 010a2a587057..3fa9c600741e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -382,6 +382,18 @@ stubs_defaults {
],
}
+soong_config_module_type {
+ name: "non_updatable_exportable_droidstubs",
+ module_type: "droidstubs",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_hidden_api_exportable_stubs",
+ ],
+ properties: [
+ "dists",
+ ],
+}
+
// We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
// platform (sdk_system_current_android). That prebuilt does not include module-lib APIs,
// so use the prebuilt module-lib stubs for modules that export module-lib stubs that the
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index c1add03fa31a..1b1bc6b9afdb 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -27,7 +27,12 @@
// These modules provide source files for the stub libraries
/////////////////////////////////////////////////////////////////////
-droidstubs {
+soong_config_module_type_import {
+ from: "frameworks/base/api/Android.bp",
+ module_types: ["non_updatable_exportable_droidstubs"],
+}
+
+non_updatable_exportable_droidstubs {
name: "api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -54,15 +59,35 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "public",
}
@@ -86,7 +111,7 @@ module_libs = [
"\\)",
]
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "system-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -114,19 +139,39 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "system",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "test-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -149,31 +194,61 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "removed.txt",
- tag: ".removed-api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "test",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "module-lib-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -201,15 +276,35 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "module-lib",
}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index caa1929abd54..d5adfd09b994 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -16,51 +16,69 @@
package android.platform.coverage
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import java.io.File
import java.io.FileWriter
/** Usage: extract-flagged-apis <api text file> <output .pb file> */
fun main(args: Array<String>) {
- var cb = ApiFile.parseApi(listOf(File(args[0])))
- var builder = FlagApiMap.newBuilder()
+ val cb = ApiFile.parseApi(listOf(File(args[0])))
+ val builder = FlagApiMap.newBuilder()
for (pkg in cb.getPackages().packages) {
- var packageName = pkg.qualifiedName()
+ val packageName = pkg.qualifiedName()
pkg.allClasses()
.filter { it.methods().size > 0 }
.forEach {
- for (method in it.methods()) {
- val flagValue =
- method.modifiers
- .findAnnotation("android.annotation.FlaggedApi")
- ?.findAttribute("value")
- ?.value
- ?.value()
- if (flagValue != null && flagValue is String) {
- var api =
- JavaMethod.newBuilder()
- .setPackageName(packageName)
- .setClassName(it.fullName())
- .setMethodName(method.name())
- for (param in method.parameters()) {
- api.addParameters(param.type().toTypeString())
- }
- if (builder.containsFlagToApi(flagValue)) {
- var updatedApis =
- builder
- .getFlagToApiOrThrow(flagValue)
- .toBuilder()
- .addJavaMethods(api)
- .build()
- builder.putFlagToApi(flagValue, updatedApis)
- } else {
- var apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
- builder.putFlagToApi(flagValue, apis)
- }
- }
- }
+ extractFlaggedApisFromClass(it, it.methods(), packageName, builder)
+ extractFlaggedApisFromClass(it, it.constructors(), packageName, builder)
}
}
val flagApiMap = builder.build()
FileWriter(args[1]).use { it.write(flagApiMap.toString()) }
}
+
+fun extractFlaggedApisFromClass(
+ classItem: ClassItem,
+ methods: List<MethodItem>,
+ packageName: String,
+ builder: FlagApiMap.Builder
+) {
+ val classFlag =
+ classItem.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value() as? String
+ for (method in methods) {
+ val methodFlag =
+ method.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value() as? String
+ ?: classFlag
+ val api =
+ JavaMethod.newBuilder()
+ .setPackageName(packageName)
+ .setClassName(classItem.fullName())
+ .setMethodName(method.name())
+ for (param in method.parameters()) {
+ api.addParameters(param.type().toTypeString())
+ }
+ if (methodFlag != null) {
+ addFlaggedApi(builder, api, methodFlag)
+ }
+ }
+}
+
+fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
+ if (builder.containsFlagToApi(flag)) {
+ val updatedApis = builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, updatedApis)
+ } else {
+ val apis = FlaggedApis.newBuilder().addJavaMethods(api).build()
+ builder.putFlagToApi(flag, apis)
+ }
+}
diff --git a/cmds/incident_helper/OWNERS b/cmds/incident_helper/OWNERS
index cede4eae50ad..29f44aba75d1 100644
--- a/cmds/incident_helper/OWNERS
+++ b/cmds/incident_helper/OWNERS
@@ -1,3 +1,2 @@
joeo@google.com
-kwekua@google.com
yanmin@google.com
diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp
index 0d427d1021a6..b273fd469de3 100644
--- a/cmds/incidentd/src/PrivacyFilter.cpp
+++ b/cmds/incidentd/src/PrivacyFilter.cpp
@@ -195,7 +195,9 @@ status_t FieldStripper::strip(const uint8_t privacyPolicy) {
ProtoOutputStream proto(mEncodedBuffer);
// Optimization when no strip happens.
- if (mRestrictions == NULL || spec.RequireAll()) {
+ if (mRestrictions == NULL || spec.RequireAll()
+ // Do not iterate through fields if primitive data
+ || !mRestrictions->children /* != FieldDescriptor::TYPE_MESSAGE */) {
if (spec.CheckPremission(mRestrictions)) {
mSize = mData->size();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index b19c3ab96bb5..13d5e03da7ed 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9877,7 +9877,7 @@ package android.companion {
ctor public ObservingDevicePresenceRequest.Builder();
method @NonNull public android.companion.ObservingDevicePresenceRequest build();
method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
}
public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b7c2ee920c79..b767c52ea9ba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -319,7 +319,6 @@ package android {
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
- field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final String RECEIVE_SENSITIVE_NOTIFICATIONS = "android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS";
field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 40ee57e46943..ac679095faa0 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -157,7 +157,7 @@ package android.app {
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -486,12 +486,16 @@ package android.app {
}
public final class UiAutomation {
+ method public void addOverridePermissionState(int, @NonNull String, int);
+ method public void clearAllOverridePermissionStates();
+ method public void clearOverridePermissionStates(int);
method public void destroy();
method @NonNull public java.util.Set<java.lang.String> getAdoptedShellPermissions();
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean);
method public void injectInputEventToInputFilter(@NonNull android.view.InputEvent);
method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
+ method public void removeOverridePermissionState(int, @NonNull String);
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
method public void syncInputTransactions();
method public void syncInputTransactions(boolean);
@@ -1761,15 +1765,12 @@ package android.hardware.input {
public final class InputManager {
method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
- method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
- method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 77255617af0a..0dab3de599dc 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5071,7 +5071,7 @@ public class ActivityManager {
* <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
* user before starting a new one, this method does not stop the previous user running in
* background in the display, and it will return {@code false} in this case. It's up to the
- * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+ * caller to call {@link #stopUser(int)} before starting a new user.
*
* @param userId user to be started in the display. It will return {@code false} if the user is
* a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
@@ -5281,15 +5281,16 @@ public class ActivityManager {
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- public boolean stopUser(@UserIdInt int userId, boolean force) {
+ public boolean stopUser(@UserIdInt int userId) {
if (userId == UserHandle.USER_SYSTEM) {
return false;
}
try {
- return USER_OP_SUCCESS == getService().stopUser(
- userId, force, /* callback= */ null);
+ return USER_OP_SUCCESS == getService().stopUserWithCallback(
+ userId, /* callback= */ null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e094ac61b500..9ea55f5ff84c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2205,19 +2205,6 @@ public class ActivityOptions extends ComponentOptions {
}
/**
- * Sets background activity launch logic won't use pending intent creator foreground state.
- *
- * @hide
- * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
- */
- @Deprecated
- public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
- mPendingIntentCreatorBackgroundActivityStartMode = ignore
- ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- return this;
- }
-
- /**
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8913d6d15dc0..3575545e202d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1082,6 +1082,8 @@ public final class ActivityThread extends ClientTransactionHandler
public boolean managed;
public boolean mallocInfo;
public boolean runGc;
+ // compression format to dump bitmaps, null if no bitmaps to be dumped
+ public String dumpBitmaps;
String path;
ParcelFileDescriptor fd;
RemoteCallback finishCallback;
@@ -1486,11 +1488,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
- public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
- ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String dumpBitmaps,
+ String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
DumpHeapData dhd = new DumpHeapData();
dhd.managed = managed;
dhd.mallocInfo = mallocInfo;
+ dhd.dumpBitmaps = dumpBitmaps;
dhd.runGc = runGc;
dhd.path = path;
try {
@@ -6859,6 +6862,9 @@ public final class ActivityThread extends ClientTransactionHandler
System.runFinalization();
System.gc();
}
+ if (dhd.dumpBitmaps != null) {
+ Bitmap.dumpAll(dhd.dumpBitmaps);
+ }
try (ParcelFileDescriptor fd = dhd.fd) {
if (dhd.managed) {
Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
@@ -6886,6 +6892,9 @@ public final class ActivityThread extends ClientTransactionHandler
if (dhd.finishCallback != null) {
dhd.finishCallback.sendResult(null);
}
+ if (dhd.dumpBitmaps != null) {
+ Bitmap.dumpAll(null); // clear dump
+ }
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
@@ -6982,7 +6991,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
} else {
// No package, perhaps it was removed?
- Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ Slog.d(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ " but missing application info. Assuming REMOVED.");
mPackages.remove(packages[i]);
mResourcePackages.remove(packages[i]);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7ed10e784b1c..7ae514ac2491 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1502,12 +1502,10 @@ public class AppOpsManager {
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the privileged assistant app to receive the training data from the sandboxed hotword
- * detection service.
+ * This op has been deprecated.
*
- * @hide
*/
- public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ private static final int OP_DEPRECATED_3 =
AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/**
@@ -1735,7 +1733,6 @@ public class AppOpsManager {
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
OPSTR_MEDIA_ROUTING_CONTROL,
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
@@ -2395,13 +2392,10 @@ public class AppOpsManager {
"android:receive_sandbox_trigger_audio";
/**
- * Allows the privileged assistant app to receive training data from the sandboxed hotword
- * detection service.
- *
+ * App op has been deprecated.
* @hide
*/
- public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
- "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
+ public static final String OPSTR_DEPRECATED_3 = "android:deprecated_3";
/**
* Creation of an overlay using accessibility services
@@ -2582,7 +2576,6 @@ public class AppOpsManager {
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT,
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OP_RUN_BACKUP_JOBS,
@@ -3021,11 +3014,8 @@ public class AppOpsManager {
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
.setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
- new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
- OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
- "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
- .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
- .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
+ new AppOpInfo.Builder(OP_DEPRECATED_3, OPSTR_DEPRECATED_3, "DEPRECATED_3")
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
new AppOpInfo.Builder(OP_CREATE_ACCESSIBILITY_OVERLAY,
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
"CREATE_ACCESSIBILITY_OVERLAY")
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 8daee5867238..f8a8f5dedc4c 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -53,9 +53,10 @@ public abstract class AppOpsManagerInternal {
* @param superImpl The super implementation.
* @return The app op check result.
*/
- int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
- int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String,
- Integer, Boolean, Integer> superImpl);
+ int checkOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw,
+ @NonNull HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer>
+ superImpl);
/**
* Allows overriding check audio operation behavior.
@@ -67,8 +68,8 @@ public abstract class AppOpsManagerInternal {
* @param superImpl The super implementation.
* @return The app op check result.
*/
- int checkAudioOperation(int code, int usage, int uid, String packageName,
- QuadFunction<Integer, Integer, Integer, String, Integer> superImpl);
+ int checkAudioOperation(int code, int usage, int uid, @Nullable String packageName,
+ @NonNull QuadFunction<Integer, Integer, Integer, String, Integer> superImpl);
/**
* Allows overriding note operation behavior.
@@ -125,7 +126,7 @@ public abstract class AppOpsManagerInternal {
* @param superImpl The super implementation.
* @return The app op note result.
*/
- SyncNotedAppOp startOperation(IBinder token, int code, int uid,
+ SyncNotedAppOp startOperation(@NonNull IBinder token, int code, int uid,
@Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage,
@@ -152,8 +153,9 @@ public abstract class AppOpsManagerInternal {
*/
SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation,
+ @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId,
@NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 716dee4dc082..e3380e0bf12a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -21,12 +21,14 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.StrictMode.vmIncorrectContextUseEnabled;
import static android.view.WindowManager.LayoutParams.WindowType;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UiContext;
+import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
@@ -2288,7 +2290,35 @@ class ContextImpl extends Context {
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
- return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
+
+ // When checking a device-aware permission on a remote device, if the permission is CAMERA
+ // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+ // device doesn't have capability fall back to checking permission on the default device.
+ // Note: we only perform permission check redirection when the device id is not explicitly
+ // set in the context.
+ int deviceId = getDeviceId();
+ if (deviceId != Context.DEVICE_ID_DEFAULT
+ && !mIsExplicitDeviceId
+ && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
+ VirtualDeviceManager virtualDeviceManager =
+ getSystemService(VirtualDeviceManager.class);
+ VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+ if (virtualDevice != null) {
+ if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+ && !virtualDevice.hasCustomAudioInputSupport())
+ || (Objects.equals(permission, Manifest.permission.CAMERA)
+ && !virtualDevice.hasCustomCameraSupport())) {
+ deviceId = Context.DEVICE_ID_DEFAULT;
+ }
+ } else {
+ Slog.e(
+ TAG,
+ "virtualDevice is not found when device id is not default. deviceId = "
+ + deviceId);
+ }
+ }
+
+ return PermissionManager.checkPermission(permission, pid, uid, deviceId);
}
/** @hide */
@@ -3114,6 +3144,14 @@ class ContextImpl extends Context {
if (mIsExplicitDeviceId) {
return;
}
+
+ if ((displayId == Display.DEFAULT_DISPLAY || displayId == Display.INVALID_DISPLAY)
+ && mDeviceId == DEVICE_ID_DEFAULT) {
+ // DEFAULT_DISPLAY & INVALID_DISPLAY are associated with default device.
+ // Return early avoiding instantiating VDM when it's not needed.
+ return;
+ }
+
VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
if (vdm != null) {
int deviceId = vdm.getDeviceIdForDisplayId(displayId);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 84bc6cec7208..dca164d6acc1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -394,7 +394,7 @@ interface IActivityManager {
oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
// Cause the specified process to dump the specified heap.
boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
- boolean runGc, in String path, in ParcelFileDescriptor fd,
+ boolean runGc, in String dumpBitmaps, in String path, in ParcelFileDescriptor fd,
in RemoteCallback finishCallback);
@UnsupportedAppUsage
boolean isUserRunning(int userid, int flags);
@@ -452,12 +452,14 @@ interface IActivityManager {
in IBinder resultTo, in String resultWho, int requestCode, int flags,
in ProfilerInfo profilerInfo, in Bundle options, int userId);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ int stopUser(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
+ int stopUserWithCallback(int userid, in IStopUserCallback callback);
+ int stopUserExceptCertainProfiles(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
/**
- * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, IStopUserCallback)}
* for details.
*/
- int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+ int stopUserWithDelayedLocking(int userid, in IStopUserCallback callback);
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
@@ -499,6 +501,7 @@ interface IActivityManager {
in String shareDescription);
void requestInteractiveBugReport();
+ void requestBugReportWithExtraAttachment(in Uri extraAttachment);
void requestFullBugReport();
void requestRemoteBugReport(long nonce);
boolean launchBugReportHandlerApp();
@@ -970,4 +973,40 @@ interface IActivityManager {
* time in the past.
*/
long getUidLastIdleElapsedTime(int uid, in String callingPackage);
+
+ /**
+ * Adds permission to be overridden to the given state. Must be called from root user.
+ *
+ * @param originatingUid The UID of the instrumented app that initialized the override
+ * @param uid The UID of the app whose permission will be overridden
+ * @param permission The permission whose state will be overridden
+ * @param result The state to override the permission to
+ *
+ * @see PackageManager.PermissionResult
+ */
+ void addOverridePermissionState(int originatingUid, int uid, String permission, int result);
+
+ /**
+ * Removes overridden permission. Must be called from root user.
+ *
+ * @param originatingUid The UID of the instrumented app that initialized the override
+ * @param uid The UID of the app whose permission is overridden
+ * @param permission The permission whose state will no longer be overridden
+ */
+ void removeOverridePermissionState(int originatingUid, int uid, String permission);
+
+ /**
+ * Clears all overridden permissions for the given UID. Must be called from root user.
+ *
+ * @param originatingUid The UID of the instrumented app that initialized the override
+ * @param uid The UID of the app whose permissions will no longer be overridden
+ */
+ void clearOverridePermissionStates(int originatingUid, int uid);
+
+ /**
+ * Clears all overridden permissions on the device. Must be called from root user.
+ *
+ * @param originatingUid The UID of the instrumented app that initialized the override
+ */
+ void clearAllOverridePermissionStates(int originatingUid);
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 251e4e8ad834..a64261a77a29 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,7 +119,8 @@ oneway interface IApplicationThread {
void scheduleSuicide();
void dispatchPackageBroadcast(int cmd, in String[] packages);
void scheduleCrash(in String msg, int typeId, in Bundle extras);
- void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path,
+ void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc,
+ in String dumpBitmaps, in String path,
in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 63cae63e1e50..69c3bd3976bc 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -62,4 +62,8 @@ interface IUiAutomationConnection {
void executeShellCommandWithStderr(String command, in ParcelFileDescriptor sink,
in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink);
List<String> getAdoptedShellPermissions();
+ void addOverridePermissionState(int uid, String permission, int result);
+ void removeOverridePermissionState(int uid, String permission);
+ void clearOverridePermissionStates(int uid);
+ void clearAllOverridePermissionStates();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 18f16ba31c0c..fe261bee41d8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3807,6 +3807,13 @@ public class Notification implements Parcelable
}
/**
+ * @hide
+ */
+ public void setTimeoutAfter(long timeout) {
+ mTimeout = timeout;
+ }
+
+ /**
* Returns what icon should be shown for this notification if it is being displayed in a
* Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
* {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}.
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index b0edc3d2b29b..348d4d8fd809 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -33,6 +33,7 @@ import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -653,6 +654,81 @@ public final class UiAutomation {
}
/**
+ * Adds permission to be overridden to the given state. UiAutomation must be connected to
+ * root user.
+ *
+ * @param uid The UID of the app whose permission will be overridden
+ * @param permission The permission whose state will be overridden
+ * @param result The state to override the permission to
+ *
+ * @see PackageManager#PERMISSION_GRANTED
+ * @see PackageManager#PERMISSION_DENIED
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi")
+ public void addOverridePermissionState(int uid, @NonNull String permission,
+ @PackageManager.PermissionResult int result) {
+ try {
+ mUiAutomationConnection.addOverridePermissionState(uid, permission, result);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes overridden permission. UiAutomation must be connected to root user.
+ *
+ * @param uid The UID of the app whose permission is overridden
+ * @param permission The permission whose state will no longer be overridden
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi")
+ public void removeOverridePermissionState(int uid, @NonNull String permission) {
+ try {
+ mUiAutomationConnection.removeOverridePermissionState(uid, permission);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all overridden permissions for the given UID. UiAutomation must be connected to
+ * root user.
+ *
+ * @param uid The UID of the app whose permissions will no longer be overridden
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi")
+ public void clearOverridePermissionStates(int uid) {
+ try {
+ mUiAutomationConnection.clearOverridePermissionStates(uid);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears all overridden permissions on the device. UiAutomation must be connected to root user.
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi")
+ public void clearAllOverridePermissionStates() {
+ try {
+ mUiAutomationConnection.clearAllOverridePermissionStates();
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Performs a global action. Such an action can be performed at any moment
* regardless of the current application or user location in that application.
* For example going back, going home, opening recents, etc.
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 33e260f352aa..3c4bd9eb0747 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -437,6 +437,71 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
}
+ @Override
+ public void addOverridePermissionState(int uid, String permission, int result)
+ throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mActivityManager.addOverridePermissionState(callingUid, uid, permission, result);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void removeOverridePermissionState(int uid, String permission) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mActivityManager.removeOverridePermissionState(callingUid, uid, permission);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearOverridePermissionStates(int uid) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mActivityManager.clearOverridePermissionStates(callingUid, uid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearAllOverridePermissionStates() throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mActivityManager.clearAllOverridePermissionStates(callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
public class Repeater implements Runnable {
// Continuously read readFrom and write back to writeTo until EOF is encountered
private final InputStream readFrom;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 42e82f6d77c0..ea6f45e8e201 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10284,6 +10284,16 @@ public class DevicePolicyManager {
* get the list of app restrictions set by each admin via
* {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin}.
*
+ * <p>Starting from Android Version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the device policy management role holder can also set app restrictions on any applications
+ * in the calling user, as well as the parent user of an organization-owned managed profile via
+ * the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)}. App restrictions set by the device policy
+ * management role holder are not returned by
+ * {@link UserManager#getApplicationRestrictions(String)}. The target application should use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} to retrieve
+ * them, alongside any app restrictions the profile or device owner might have set.
+ *
* <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
@@ -10299,11 +10309,14 @@ public class DevicePolicyManager {
@WorkerThread
public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
Bundle settings) {
- throwIfParentInstance("setApplicationRestrictions");
+ if (!Flags.dmrhCanSetAppRestriction()) {
+ throwIfParentInstance("setApplicationRestrictions");
+ }
+
if (mService != null) {
try {
mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
- settings);
+ settings, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11704,11 +11717,14 @@ public class DevicePolicyManager {
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@Nullable ComponentName admin, String packageName) {
- throwIfParentInstance("getApplicationRestrictions");
+ if (!Flags.dmrhCanSetAppRestriction()) {
+ throwIfParentInstance("getApplicationRestrictions");
+ }
+
if (mService != null) {
try {
return mService.getApplicationRestrictions(admin, mContext.getPackageName(),
- packageName);
+ packageName, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -13986,8 +14002,15 @@ public class DevicePolicyManager {
public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
throwIfParentInstance("getParentProfileInstance");
try {
- if (!mService.isManagedProfile(admin)) {
- throw new SecurityException("The current user does not have a parent profile.");
+ if (Flags.dmrhCanSetAppRestriction()) {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ if (!um.isManagedProfile()) {
+ throw new SecurityException("The current user does not have a parent profile.");
+ }
+ } else {
+ if (!mService.isManagedProfile(admin)) {
+ throw new SecurityException("The current user does not have a parent profile.");
+ }
}
return new DevicePolicyManager(mContext, mService, true);
} catch (RemoteException e) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d4589dc6d453..2002326d76bd 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -244,8 +244,8 @@ interface IDevicePolicyManager {
void setDefaultSmsApplication(in ComponentName admin, String callerPackageName, String packageName, boolean parent);
void setDefaultDialerApplication(String packageName);
- void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
- Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
+ void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings, in boolean parent);
+ Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in boolean parent);
boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
String getApplicationRestrictionsManagingPackage(in ComponentName admin);
boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1425063c6190..56fb4aa45fb3 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -36,13 +36,6 @@ flag {
}
flag {
- name: "cross_user_suspension_enabled"
- namespace: "enterprise"
- description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
- bug: "263464464"
-}
-
-flag {
name: "cross_user_suspension_enabled_ro"
namespace: "enterprise"
description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
@@ -211,6 +204,13 @@ flag {
}
flag {
+ name: "dmrh_can_set_app_restriction"
+ namespace: "enterprise"
+ description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
+ bug: "328758346"
+}
+
+flag {
name: "allow_screen_brightness_control_on_cope"
namespace: "enterprise"
description: "Allow COPE admin to control screen brightness and timeout."
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 75485626d2ef..508077ed43cc 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2655,13 +2655,12 @@ public class AssistStructure implements Parcelable {
);
}
GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();
- if (getCredentialRequest == null) {
- Log.i(TAG, prefix + " No Credential Manager Request");
- } else {
- Log.i(TAG, prefix + " GetCredentialRequest: no. of options= "
- + getCredentialRequest.getCredentialOptions().size()
- );
- }
+ Log.i(TAG, prefix + " Credential Manager info:"
+ + " hasCredentialManagerRequest=" + (getCredentialRequest != null)
+ + (getCredentialRequest != null
+ ? ", sizeOfOptions=" + getCredentialRequest.getCredentialOptions().size()
+ : "")
+ );
final int NCHILDREN = node.getChildCount();
if (NCHILDREN > 0) {
diff --git a/core/java/android/app/wearable/IWearableSensingCallback.aidl b/core/java/android/app/wearable/IWearableSensingCallback.aidl
new file mode 100644
index 000000000000..d76b32da266d
--- /dev/null
+++ b/core/java/android/app/wearable/IWearableSensingCallback.aidl
@@ -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 android.app.wearable;
+
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * Interface for callbacks coming from the WearableSensingService.
+ *
+ * @hide
+ */
+oneway interface IWearableSensingCallback {
+
+ /**
+ * Opens the requested file and returns the resulting ParcelFileDescriptor to the provided
+ * future.
+ */
+ void openFile(in String filename, in AndroidFuture<ParcelFileDescriptor> future);
+} \ No newline at end of file
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index 7d3b28511537..c0d06ea88388 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -17,6 +17,7 @@
package android.app.wearable;
import android.app.PendingIntent;
+import android.app.wearable.IWearableSensingCallback;
import android.content.ComponentName;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -30,9 +31,9 @@ import android.os.SharedMemory;
*/
interface IWearableSensingManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
- void provideConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideConnection(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
- void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in @nullable IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index df6d2a635024..4b77c74bb174 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -27,11 +27,15 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
+import android.app.compat.CompatChanges;
import android.companion.CompanionDeviceManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
@@ -39,7 +43,13 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.wearable.WearableSensingService;
import android.system.OsConstants;
+import android.util.Slog;
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Retention;
@@ -155,6 +165,17 @@ public class WearableSensingManager {
public @interface StatusCode {}
/**
+ * If the WearableSensingService implementation belongs to the same APK as the caller, calling
+ * {@link #provideDataStream(ParcelFileDescriptor, Executor, Consumer)} will allow
+ * WearableSensingService to read from the caller's file directory via {@link
+ * Context#openFileInput(String)}. The read will be proxied via the caller's process and
+ * executed by the {@code executor} provided to this method.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long ALLOW_WEARABLE_SENSING_SERVICE_FILE_READ = 330701114L;
+
+ /**
* Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent
* provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
*
@@ -169,6 +190,7 @@ public class WearableSensingManager {
EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class);
}
+ private static final String TAG = WearableSensingManager.class.getSimpleName();
private final Context mContext;
private final IWearableSensingManager mService;
@@ -216,6 +238,11 @@ public class WearableSensingManager {
* dropped during the restart. The caller is responsible for ensuring other method calls are
* queued until a success status is returned from the {@code statusConsumer}.
*
+ * <p>If the WearableSensingService implementation belongs to the same APK as the caller,
+ * calling this method will allow WearableSensingService to read from the caller's file
+ * directory via {@link Context#openFileInput(String)}. The read will be proxied via the
+ * caller's process and executed by the {@code executor} provided to this method.
+ *
* @param wearableConnection The connection to provide
* @param executor Executor on which to run the consumer callback
* @param statusConsumer A consumer that handles the status codes for providing the connection
@@ -227,9 +254,14 @@ public class WearableSensingManager {
@NonNull ParcelFileDescriptor wearableConnection,
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
try {
- RemoteCallback callback = createStatusCallback(executor, statusConsumer);
- mService.provideConnection(wearableConnection, callback);
+ // The wearableSensingCallback is included in this method call even though it is not
+ // semantically related to the connection because we want to avoid race conditions
+ // during the process restart triggered by this method call. See
+ // com.android.server.wearable.RemoteWearableSensingService for details.
+ mService.provideConnection(
+ wearableConnection, createWearableSensingCallback(executor), statusCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -237,15 +269,21 @@ public class WearableSensingManager {
/**
* Provides a data stream to the WearableSensingService that's backed by the
- * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
- * This is used by applications that will also provide an implementation of
- * an isolated WearableSensingService. If the data stream was provided successfully
- * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+ * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call. This
+ * is used by applications that will also provide an implementation of an isolated
+ * WearableSensingService. If the data stream was provided successfully {@link
+ * WearableSensingManager#STATUS_SUCCESS} will be provided.
+ *
+ * <p>Starting from target SDK level 35, if the WearableSensingService implementation belongs to
+ * the same APK as the caller, calling this method will allow WearableSensingService to read
+ * from the caller's file directory via {@link Context#openFileInput(String)}. The read will be
+ * proxied via the caller's process and executed by the {@code executor} provided to this
+ * method.
*
* @param parcelFileDescriptor The data stream to provide
* @param executor Executor on which to run the consumer callback
- * @param statusConsumer A consumer that handles the status codes, which is returned
- * right after the call.
+ * @param statusConsumer A consumer that handles the status codes, which is returned right after
+ * the call.
* @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
* to provide a remote wearable device connection to the WearableSensingService
*/
@@ -255,9 +293,14 @@ public class WearableSensingManager {
@NonNull ParcelFileDescriptor parcelFileDescriptor,
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
+ RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
+ IWearableSensingCallback wearableSensingCallback = null;
+ if (CompatChanges.isChangeEnabled(ALLOW_WEARABLE_SENSING_SERVICE_FILE_READ)) {
+ wearableSensingCallback = createWearableSensingCallback(executor);
+ }
try {
- RemoteCallback callback = createStatusCallback(executor, statusConsumer);
- mService.provideDataStream(parcelFileDescriptor, callback);
+ mService.provideDataStream(
+ parcelFileDescriptor, wearableSensingCallback, statusCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -480,4 +523,47 @@ public class WearableSensingManager {
}
});
}
+
+ private IWearableSensingCallback createWearableSensingCallback(Executor executor) {
+ return new IWearableSensingCallback.Stub() {
+
+ @Override
+ public void openFile(String filename, AndroidFuture<ParcelFileDescriptor> future) {
+ Slog.d(TAG, "IWearableSensingCallback#openFile " + filename);
+ Binder.withCleanCallingIdentity(
+ () ->
+ executor.execute(
+ () -> {
+ File file = new File(mContext.getFilesDir(), filename);
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd =
+ ParcelFileDescriptor.open(
+ file,
+ ParcelFileDescriptor
+ .MODE_READ_ONLY);
+ Slog.d(
+ TAG,
+ "Successfully opened a file with"
+ + " ParcelFileDescriptor.");
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file.", e);
+ } finally {
+ future.complete(pfd);
+ if (pfd != null) {
+ try {
+ pfd.close();
+ } catch (IOException ex) {
+ Slog.e(
+ TAG,
+ "Error closing"
+ + " ParcelFileDescriptor.",
+ ex);
+ }
+ }
+ }
+ }));
+ }
+ };
+ }
}
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
index f1d594e80bda..11ea735dff4f 100644
--- a/core/java/android/companion/ObservingDevicePresenceRequest.java
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -183,7 +183,11 @@ public final class ObservingDevicePresenceRequest implements Parcelable {
* @param uuid The ParcelUuid for observing device presence.
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_SCAN
+ })
public Builder setUuid(@NonNull ParcelUuid uuid) {
checkNotUsed();
this.mUuid = uuid;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8bc5e8c1ef34..9e316a2d3560 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3771,6 +3771,10 @@ public class Intent implements Parcelable, Cloneable {
* <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
* the phone number originally intended to be dialed.</li>
* </ul>
+ * <p class="note">Starting in Android 15, this broadcast is no longer sent as an ordered
+ * broadcast. The <code>resultData</code> no longer has any effect and will not determine the
+ * actual routing of the call. Further, receivers of this broadcast do not get foreground
+ * priority and cannot launch background activities.</p>
* <p>Once the broadcast is finished, the resultData is used as the actual
* number to call. If <code>null</code>, no call will be placed.</p>
* <p>It is perfectly acceptable for multiple receivers to process the
@@ -3811,8 +3815,8 @@ public class Intent implements Parcelable, Cloneable {
* {@link android.telecom.CallRedirectionService} API. Apps that perform call screening
* should use the {@link android.telecom.CallScreeningService} API. Apps which need to be
* notified of basic call state should use
- * {@link android.telephony.PhoneStateListener#onCallStateChanged(int, String)} to determine
- * when a new outgoing call is placed.
+ * {@link android.telephony.TelephonyCallback.CallStateListener} to determine when a new
+ * outgoing call is placed.
*/
@Deprecated
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 915992904a5c..1d4403cb79e8 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -887,7 +887,7 @@ public final class UserProperties implements Parcelable {
* <p> Setting this property to true will enable the user's CE storage to remain unlocked when
* the user is stopped using
* {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
- * boolean, IStopUserCallback)}.
+ * IStopUserCallback)}.
*
* <p> When this property is false, delayed locking may still be applicable at a global
* level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 625d7cb66900..c7237ea35c2a 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@ public class FontScaleConverterFactory {
synchronized (LOOKUP_TABLES_WRITE_LOCK) {
putInto(
sLookupTables,
+ /* scaleKey= */ 1.05f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 8.4f, 10.5f, 12.6f, 14.8f, 18.6f, 20.6f, 24.4f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.1f,
new FontScaleConverterImpl(
/* fromSp= */
@@ -78,6 +88,16 @@ public class FontScaleConverterFactory {
putInto(
sLookupTables,
+ /* scaleKey= */ 1.2f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 9.6f, 12f, 14.4f, 17.2f, 20.4f, 22.4f, 25.6f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.3f,
new FontScaleConverterImpl(
/* fromSp= */
@@ -117,7 +137,7 @@ public class FontScaleConverterFactory {
);
}
- sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.02f;
+ sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.01f;
if (sMinScaleBeforeCurvesApplied <= 1.0f) {
throw new IllegalStateException(
"You should only apply non-linear scaling to font scales > 1"
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 90b7869e1c5d..a0e40f6390ee 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -52,6 +52,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -70,7 +71,8 @@ import javax.crypto.Mac;
public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
private static final String TAG = "BiometricPrompt";
- private static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
+ @VisibleForTesting
+ static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
/**
* Error/help message will show for this amount of time.
@@ -223,8 +225,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
*
* @param logoDescription The logo description text that will be shown on the prompt.
* @return This builder.
- * @throws IllegalStateException If logo description is null or exceeds certain character
- * limit.
+ * @throws IllegalArgumentException If logo description is null or exceeds certain character
+ * limit.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@@ -232,7 +234,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
if (logoDescription == null
|| logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"Logo description passed in can not be null or exceed "
+ MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number.");
}
@@ -240,7 +242,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
return this;
}
-
/**
* Required: Sets the title that will be shown on the prompt.
* @param title The title to display.
diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
index 853d86cf94dc..a9eca3f87fc3 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
@@ -29,19 +29,22 @@ import android.hardware.biometrics.BiometricPrompt.ButtonInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.concurrent.Executor;
/**
- * Contains the information of the template of content view with a more options button for Biometric
- * Prompt.
+ * Contains the information of the template of content view with a more options button for
+ * Biometric Prompt.
+ * <p>
* This button should be used to provide more options for sign in or other purposes, such as when a
* user needs to select between multiple app-specific accounts or profiles that are available for
- * sign in. This is not common and apps should avoid using it if there is only one choice available
- * or if the user has already selected the appropriate account to use before invoking
- * BiometricPrompt because it will create additional steps that the user must navigate through.
- * Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask
- * the user for the correct account, and finally allow the app to decide how to proceed once
- * selected.
+ * sign in.
+ * <p>
+ * Apps should avoid using this when possible because it will create additional steps that the user
+ * must navigate through - clicking the more options button will dismiss the prompt, provide the app
+ * an opportunity to ask the user for the correct option, and finally allow the app to decide how to
+ * proceed once selected.
*
* <p>
* Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric
@@ -59,7 +62,8 @@ import java.util.concurrent.Executor;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
- private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
+ @VisibleForTesting
+ static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
private final String mDescription;
private DialogInterface.OnClickListener mListener;
@@ -132,14 +136,16 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
}
};
+ /**
+ * A builder that collects arguments to be shown on the content view with more options button.
+ */
public static final class Builder {
private String mDescription;
private Executor mExecutor;
private DialogInterface.OnClickListener mListener;
/**
- * Optional: Sets a description that will be shown on the content view. Note that there are
- * limits on the number of characters allowed for description.
+ * Optional: Sets a description that will be shown on the content view.
*
* @param description The description to display.
* @return This builder.
@@ -149,7 +155,7 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
public Builder setDescription(@NonNull String description) {
if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException("The character number of description exceeds "
+ throw new IllegalArgumentException("The character number of description exceeds "
+ MAX_DESCRIPTION_CHARACTER_NUMBER);
}
mDescription = description;
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index 02b2a50ade3c..d8b28673f8ae 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -24,6 +24,8 @@ import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
@@ -47,9 +49,12 @@ import java.util.List;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptVerticalListContentView implements PromptContentViewParcelable {
- private static final int MAX_ITEM_NUMBER = 20;
- private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
- private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
+ @VisibleForTesting
+ static final int MAX_ITEM_NUMBER = 20;
+ @VisibleForTesting
+ static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+ @VisibleForTesting
+ static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
private final List<PromptContentItemParcelable> mContentList;
private final String mDescription;
@@ -155,7 +160,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
@NonNull
public Builder setDescription(@NonNull String description) {
if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException("The character number of description exceeds "
+ throw new IllegalArgumentException("The character number of description exceeds "
+ MAX_DESCRIPTION_CHARACTER_NUMBER);
}
mDescription = description;
@@ -195,12 +200,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
private void checkItemLimits(@NonNull PromptContentItem listItem) {
if (doesListItemExceedsCharLimit(listItem)) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"The character number of list item exceeds "
+ MAX_EACH_ITEM_CHARACTER_NUMBER);
}
if (mContentList.size() > MAX_ITEM_NUMBER) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"The number of list items exceeds " + MAX_ITEM_NUMBER);
}
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ea7f8c4c5dfc..c6a8762e4d79 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -19,8 +19,6 @@ package android.hardware.camera2;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.camera.VirtualCameraConfig;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.ExtensionKey;
@@ -5349,10 +5347,9 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Id of the device that owns this camera.</p>
* <p>In case of a virtual camera, this would be the id of the virtual device
* owning the camera. For any other camera, this key would not be present.
- * Callers should assume {@link android.content.Context#DEVICE_ID_DEFAULT}
+ * Callers should assume {@link android.content.Context#DEVICE_ID_DEFAULT }
* in case this key is not present.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
- * @see VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)
* @hide
*/
public static final Key<Integer> INFO_DEVICE_ID =
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ec9b013c34cc..dca663d206d3 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -23,12 +23,14 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
+import android.util.Size;
import android.view.Surface;
import com.android.internal.camera.flags.Flags;
@@ -530,9 +532,10 @@ public abstract class CameraDevice implements AutoCloseable {
* SurfaceTexture}: Set the size of the SurfaceTexture with {@link
* android.graphics.SurfaceTexture#setDefaultBufferSize} to be one of the sizes returned by
* {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(SurfaceTexture.class)}
- * before creating a Surface from the SurfaceTexture with {@link Surface#Surface}. If the size
- * is not set by the application, it will be set to be the smallest supported size less than
- * 1080p, by the camera device.</li>
+ * before creating a Surface from the SurfaceTexture with
+ * {@link Surface#Surface(SurfaceTexture)}. If the size is not set by the application,
+ * it will be set to be the smallest supported size less than 1080p, by the camera
+ * device.</li>
*
* <li>For recording with {@link android.media.MediaCodec}: Call
* {@link android.media.MediaCodec#createInputSurface} after configuring
@@ -1405,10 +1408,16 @@ public abstract class CameraDevice implements AutoCloseable {
*
* <p><b>NOTE:</b>
* For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
- * this method will ensure session parameters set through calls to
- * {@link SessionConfiguration#setSessionParameters} are also supported if the Camera Device
- * supports it. For apps targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
- * below, session parameters will be ignored.</p>
+ * this method will automatically delegate to
+ * {@link CameraDeviceSetup#isSessionConfigurationSupported} whenever possible. This
+ * means that the output of this method will consider parameters set through
+ * {@link SessionConfiguration#setSessionParameters} as well.
+ * </p>
+ *
+ * <p>Session Parameters will be ignored for apps targeting <=
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, or if
+ * {@link CameraManager#isCameraDeviceSetupSupported} returns false for the camera id
+ * associated with this {@code CameraDevice}.</p>
*
* @return {@code true} if the given session configuration is supported by the camera device
* {@code false} otherwise.
@@ -1419,6 +1428,8 @@ public abstract class CameraDevice implements AutoCloseable {
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
*
+ * @see CameraManager#isCameraDeviceSetupSupported(String)
+ * @see CameraDeviceSetup#isSessionConfigurationSupported(SessionConfiguration)
*/
public boolean isSessionConfigurationSupported(
@NonNull SessionConfiguration sessionConfig) throws CameraAccessException {
@@ -1703,7 +1714,7 @@ public abstract class CameraDevice implements AutoCloseable {
* SessionConfiguration} can then be created using the OutputConfiguration objects and
* be used to query whether it's supported by the camera device. To create the
* CameraCaptureSession, the application still needs to make sure all output surfaces
- * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred
+ * are added via {@link OutputConfiguration#addSurface} with the exception of deferred
* surfaces for {@link android.view.SurfaceView} and
* {@link android.graphics.SurfaceTexture}.</li>
* </ul>
@@ -1751,7 +1762,7 @@ public abstract class CameraDevice implements AutoCloseable {
* SessionConfiguration} can then be created using the OutputConfiguration objects and
* be used for this function. To create the CameraCaptureSession, the application still
* needs to make sure all output surfaces are added via {@link
- * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link
+ * OutputConfiguration#addSurface} with the exception of deferred surfaces for {@link
* android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p>
*
* @param sessionConfig The session configuration for which characteristics are fetched.
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 64fc4c29db90..e583627c0960 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -399,16 +399,8 @@ public final class DeviceState {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mIdentifier);
dest.writeString8(mName);
-
- dest.writeInt(mSystemProperties.size());
- for (int i = 0; i < mSystemProperties.size(); i++) {
- dest.writeInt(mSystemProperties.valueAt(i));
- }
-
- dest.writeInt(mPhysicalProperties.size());
- for (int i = 0; i < mPhysicalProperties.size(); i++) {
- dest.writeInt(mPhysicalProperties.valueAt(i));
- }
+ dest.writeArraySet(mSystemProperties);
+ dest.writeArraySet(mPhysicalProperties);
}
@NonNull
@@ -417,16 +409,11 @@ public final class DeviceState {
public DeviceState.Configuration createFromParcel(Parcel source) {
int identifier = source.readInt();
String name = source.readString8();
- ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
- int systemPropertySize = source.readInt();
- for (int i = 0; i < systemPropertySize; i++) {
- systemProperties.add(source.readInt());
- }
- ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
- int physicalPropertySize = source.readInt();
- for (int j = 0; j < physicalPropertySize; j++) {
- physicalProperties.add(source.readInt());
- }
+ ArraySet<@SystemDeviceStateProperties Integer> systemProperties =
+ (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+ ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties =
+ (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+
return new DeviceState.Configuration(identifier, name, systemProperties,
physicalProperties);
}
diff --git a/core/java/android/hardware/devicestate/OWNERS b/core/java/android/hardware/devicestate/OWNERS
new file mode 100644
index 000000000000..d9b0e2e5ffa5
--- /dev/null
+++ b/core/java/android/hardware/devicestate/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/devicestate/OWNERS
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2816f777e8ab..1c37aa25af5f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -94,33 +94,8 @@ interface IInputManager {
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
- KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
- String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- // New Keyboard layout config APIs
KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
in InputMethodSubtype imeSubtype);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a1242fb43bbd..f94915876a2f 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -473,22 +473,21 @@ public final class InputManager {
}
/**
- * Returns the descriptors of all supported keyboard layouts appropriate for the specified
- * input device.
+ * Returns the descriptors of all supported keyboard layouts.
* <p>
* The input manager consults the built-in keyboard layouts as well as all keyboard layouts
* advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
* </p>
*
- * @param device The input device to query.
* @return The ids of all keyboard layouts which are supported by the specified input device.
*
* @hide
*/
@TestApi
@NonNull
- public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
- KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+ @SuppressLint("UnflaggedApi")
+ public List<String> getKeyboardLayoutDescriptors() {
+ KeyboardLayout[] layouts = getKeyboardLayouts();
List<String> res = new ArrayList<>();
for (KeyboardLayout kl : layouts) {
res.add(kl.getDescriptor());
@@ -511,33 +510,18 @@ public final class InputManager {
@TestApi
@NonNull
public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
- KeyboardLayout[] layouts = getKeyboardLayouts();
- for (KeyboardLayout kl : layouts) {
- if (layoutDescriptor.equals(kl.getDescriptor())) {
- return kl.getLayoutType();
- }
- }
- return "";
+ KeyboardLayout layout = getKeyboardLayout(layoutDescriptor);
+ return layout == null ? "" : layout.getLayoutType();
}
/**
- * Gets information about all supported keyboard layouts appropriate
- * for a specific input device.
- * <p>
- * The input manager consults the built-in keyboard layouts as well
- * as all keyboard layouts advertised by applications using a
- * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
- * </p>
- *
- * @return A list of all supported keyboard layouts for a specific
- * input device.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- return mGlobal.getKeyboardLayoutsForInputDevice(identifier);
+ return new KeyboardLayout[0];
}
/**
@@ -549,6 +533,7 @@ public final class InputManager {
*
* @hide
*/
+ @Nullable
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
if (keyboardLayoutDescriptor == null) {
throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
@@ -562,121 +547,45 @@ public final class InputManager {
}
/**
- * Gets the current keyboard layout descriptor for the specified input device.
- *
- * @param identifier Identifier for the input device
- * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@Nullable
public String getCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return null;
}
/**
- * Sets the current keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- mGlobal.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
+ @NonNull String keyboardLayoutDescriptor) {}
/**
- * Gets all keyboard layout descriptors that are enabled for the specified input device.
- *
- * @param identifier The identifier for the input device.
- * @return The keyboard layout descriptors.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
-
- try {
- return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new String[0];
}
/**
- * Adds the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
- * Removes the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
@NonNull String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7c104a0ca946..7b29666d9a96 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1068,36 +1068,21 @@ public final class InputManagerGlobal {
}
/**
- * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new KeyboardLayout[0];
}
/**
- * @see InputManager#setCurrentKeyboardLayoutForInputDevice
- * (InputDeviceIdentifier, String)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- Objects.requireNonNull(identifier, "identifier must not be null");
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- try {
- mIm.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
+ @NonNull String keyboardLayoutDescriptor) {}
+
/**
* @see InputDevice#getSensorManager()
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2cd7aeb2d99d..cbfc5d1e5050 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -56,6 +56,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.ctrlShiftShortcut;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -400,9 +401,14 @@ public class InputMethodService extends AbstractInputMethodService {
private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
private Runnable mStylusWindowIdleTimeoutRunnable;
private long mStylusWindowIdleTimeoutForTest;
- /** Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
+ /**
+ * Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
**/
private int mLastUsedToolType;
+ /**
+ * Tracks the ctrl+shift shortcut
+ **/
+ private boolean mUsingCtrlShiftShortcut = false;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -3612,7 +3618,8 @@ public class InputMethodService extends AbstractInputMethodService {
// any KeyEvent keyDown should reset last toolType.
updateEditorToolTypeInternal(MotionEvent.TOOL_TYPE_UNKNOWN);
}
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
final ExtractEditText eet = getExtractEditTextIfVisible();
if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
return true;
@@ -3622,14 +3629,33 @@ public class InputMethodService extends AbstractInputMethodService {
return true;
}
return false;
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+ } else if (keyCode == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
if (mDecorViewVisible && mWindowVisible) {
int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
mPrivOps.switchKeyboardLayoutAsync(direction);
+ event.startTracking();
return true;
}
}
+
+ // Check if this may be a ctrl+shift shortcut
+ if (ctrlShiftShortcut()) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+ // Potentially Ctrl+Shift shortcut if Ctrl is currently pressed
+ mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON);
+ } else if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+ || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+ // Potentially Ctrl+Shift shortcut if Shift is currently pressed
+ mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_CTRL_MASK, KeyEvent.META_SHIFT_ON);
+ } else {
+ mUsingCtrlShiftShortcut = false;
+ }
+ }
+
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
@@ -3671,7 +3697,27 @@ public class InputMethodService extends AbstractInputMethodService {
* them to perform navigation in the underlying application.
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (ctrlShiftShortcut()) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
+ || keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+ || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+ if (mUsingCtrlShiftShortcut
+ && event.hasNoModifiers()) {
+ mUsingCtrlShiftShortcut = false;
+ if (mDecorViewVisible && mWindowVisible) {
+ // Move to the next IME
+ switchToNextInputMethod(false /* onlyCurrentIme */);
+ // TODO(b/332937629): Make the event stream consistent again
+ return true;
+ }
+ }
+ } else {
+ mUsingCtrlShiftShortcut = false;
+ }
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
final ExtractEditText eet = getExtractEditTextIfVisible();
if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
return true;
@@ -3679,7 +3725,12 @@ public class InputMethodService extends AbstractInputMethodService {
if (event.isTracking() && !event.isCanceled()) {
return handleBack(true);
}
+ } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (event.isTracking() && !event.isCanceled()) {
+ return true;
+ }
}
+
return doMovementKey(keyCode, event, MOVEMENT_UP);
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 8a1774291e9f..bb74a3e7f896 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1436,10 +1436,10 @@ public class RecoverySystem {
* @throws IOException if the recovery system service could not be contacted
*/
private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
- Log.i(TAG, String.format("<%s> is requesting LSFK", packageName));
+ Log.i(TAG, TextUtils.formatSimple("Package<%s> requesting LSKF", packageName));
try {
boolean validRequest = mService.requestLskf(packageName, sender);
- Log.i(TAG, String.format("LSKF Request isValid = %b", validRequest));
+ Log.i(TAG, TextUtils.formatSimple("LSKF Request isValid = %b", validRequest));
return validRequest;
} catch (RemoteException | SecurityException e) {
throw new IOException("could not request LSKF capture", e);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3441244d6c58..fe3fa8cf34f5 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -240,6 +240,16 @@ public final class PermissionManager {
public static final String EXTRA_PERMISSION_USAGES =
"android.permission.extra.PERMISSION_USAGES";
+ /**
+ * Specify what permissions are device aware. Only device aware permissions can be granted to
+ * a remote device.
+ * @hide
+ */
+ public static final Set<String> DEVICE_AWARE_PERMISSIONS =
+ Flags.deviceAwarePermissionsEnabled()
+ ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ : Collections.emptySet();
+
private final @NonNull Context mContext;
private final IPackageManager mPackageManager;
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
index 69113ef8f946..a15d9bc1b485 100644
--- a/core/java/android/permission/TEST_MAPPING
+++ b/core/java/android/permission/TEST_MAPPING
@@ -11,5 +11,29 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsVirtualDevicesAudioTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsVirtualDevicesAppLaunchTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
+ }
+ ]
+ }
]
} \ No newline at end of file
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 92bbadc4d2f2..c26f351bc3a0 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -157,3 +157,13 @@ flag {
bug: "266164193"
}
+flag {
+ name: "ignore_apex_permissions"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Ignore APEX pacakges for permissions on V+"
+ bug: "301320911"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index aa2f85dd2287..b7d421ae51b5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10225,6 +10225,13 @@ public final class Settings {
"screensaver_complications_enabled";
/**
+ * Defines the enabled state for the glanceable hub.
+ *
+ * @hide
+ */
+ public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled";
+
+ /**
* Whether home controls are enabled to be shown over the screensaver by the user.
*
* @hide
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 4e521d62684a..353828c105bb 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,6 +17,7 @@
package android.service.dreams;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import android.annotation.FlaggedApi;
import android.annotation.IdRes;
@@ -29,6 +30,7 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.AlarmManager;
+import android.app.KeyguardManager;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -280,6 +282,8 @@ public class DreamService extends Service implements Window.Callback {
private IDreamOverlayCallback mOverlayCallback;
+ private Integer mTrackingConfirmKey = null;
+
public DreamService() {
mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
@@ -296,7 +300,54 @@ public class DreamService extends Service implements Window.Callback {
/** {@inheritDoc} */
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
+ if (dreamHandlesConfirmKeys()) {
+ // In the case of an interactive dream that consumes the event, do not process further.
+ if (mInteractive && mWindow.superDispatchKeyEvent(event)) {
+ return true;
+ }
+
+ // If the key is a confirm key and on up, either unlock (no auth) or show bouncer.
+ if (KeyEvent.isConfirmKey(event.getKeyCode())) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN -> {
+ if (mTrackingConfirmKey != null) {
+ return true;
+ }
+
+ mTrackingConfirmKey = event.getKeyCode();
+ }
+ case KeyEvent.ACTION_UP -> {
+ if (mTrackingConfirmKey != event.getKeyCode()) {
+ return true;
+ }
+
+ mTrackingConfirmKey = null;
+
+ final KeyguardManager keyguardManager =
+ getSystemService(KeyguardManager.class);
+
+ // Simply wake up in the case the device is not locked.
+ if (!keyguardManager.isKeyguardLocked()) {
+ wakeUp();
+ return true;
+ }
+
+ keyguardManager.requestDismissKeyguard(getActivity(),
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissError() {
+ Log.e(TAG, "Could not dismiss keyguard on confirm key");
+ }
+ });
+ }
+ }
+
+ // All key events for matching key codes should be consumed to prevent other actions
+ // from triggering.
+ return true;
+ }
+ }
+
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
wakeUp();
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 2e16a036618d..2f45f34b8da2 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -8,3 +8,14 @@ flag {
"relying on the dream's window"
bug: "291990564"
}
+
+flag {
+ name: "dream_handles_confirm_keys"
+ namespace: "dreams"
+ description: "This flag enables dreams processing confirm keys to show the bouncer or dismiss "
+ "the keyguard"
+ bug: "326975875"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0fc51e74d570..582c90feb547 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,12 +27,15 @@ import static android.graphics.Matrix.MSKEW_Y;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -832,6 +835,15 @@ public abstract class WallpaperService extends Service {
}
/**
+ * Called when the dim amount of the wallpaper changed. This can be used to recompute the
+ * wallpaper colors based on the new dim, and call {@link #notifyColorsChanged()}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void onDimAmountChanged(float dimAmount) {
+ }
+
+ /**
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
@@ -1043,6 +1055,10 @@ public abstract class WallpaperService extends Service {
}
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+
+ // after the dim changes, allow colors to be immediately recomputed
+ mLastColorInvalidation = 0;
+ if (offloadColorExtraction()) onDimAmountChanged(mWallpaperDimAmount);
}
/**
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index dffadf0cda70..9d9cacff120b 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -17,6 +17,7 @@
package android.service.wearable;
import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.wearable.IWearableSensingCallback;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
@@ -28,8 +29,8 @@ import android.os.SharedMemory;
* @hide
*/
oneway interface IWearableSensingService {
- void provideSecureConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
- void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideSecureConnection(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback);
+ void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in IWearableSensingCallback wearableSensingCallback, in RemoteCallback statusCallback);
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index a2770172d3ca..ac22e7067622 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -20,13 +20,16 @@ import android.annotation.BinderThread;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.wearable.Flags;
+import android.app.wearable.IWearableSensingCallback;
import android.app.wearable.WearableSensingDataRequest;
import android.app.wearable.WearableSensingManager;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
@@ -34,18 +37,28 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteCallback;
+import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.ambientcontext.AmbientContextDetectionResult;
import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
import android.service.voice.HotwordAudioStream;
+import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
@@ -102,9 +115,14 @@ public abstract class WearableSensingService extends Service {
public static final String SERVICE_INTERFACE =
"android.service.wearable.WearableSensingService";
+ // Timeout to prevent thread from waiting on the openFile future indefinitely.
+ private static final Duration OPEN_FILE_TIMEOUT = Duration.ofSeconds(5);
+
private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap =
new SparseArray<>();
+ private IWearableSensingCallback mWearableSensingCallback;
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -113,8 +131,13 @@ public abstract class WearableSensingService extends Service {
/** {@inheritDoc} */
@Override
public void provideSecureConnection(
- ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ ParcelFileDescriptor secureWearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback callback) {
Objects.requireNonNull(secureWearableConnection);
+ if (wearableSensingCallback != null) {
+ mWearableSensingCallback = wearableSensingCallback;
+ }
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onSecureConnectionProvided(
secureWearableConnection, consumer);
@@ -123,8 +146,13 @@ public abstract class WearableSensingService extends Service {
/** {@inheritDoc} */
@Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback callback) {
Objects.requireNonNull(parcelFileDescriptor);
+ if (wearableSensingCallback != null) {
+ mWearableSensingCallback = wearableSensingCallback;
+ }
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataStreamProvided(
parcelFileDescriptor, consumer);
@@ -570,6 +598,64 @@ public abstract class WearableSensingService extends Service {
@NonNull String packageName,
@NonNull Consumer<AmbientContextDetectionServiceStatus> consumer);
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given {@code fileName} under
+ * the internal app storage of the APK providing the implementation for this class. {@link
+ * Context#getFilesDir()} will be added as a prefix to the provided {@code fileName}.
+ *
+ * <p>This method is only functional after {@link
+ * #onSecureConnectionProvided(ParcelFileDescriptor, Consumer)} or {@link
+ * #onDataStreamProvided(ParcelFileDescriptor, Consumer)} has been called as a result of a
+ * process owned by the same APK calling {@link
+ * WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)} or {@link
+ * WearableSensingManager#provideDataStream(ParcelFileDescriptor, Executor, Consumer)}.
+ * Otherwise, it will throw an {@link IllegalStateException}. This is because this method
+ * proxies the file read via that process. Also, the APK needs to have a targetSdkVersion of 35
+ * or newer.
+ *
+ * @param fileName Relative path of a file under {@link Context#getFilesDir()}.
+ * @throws IllegalStateException if the above condition is not satisfied.
+ * @throws FileNotFoundException if the file does not exist or cannot be opened, or an error
+ * occurred during the RPC to proxy the file read via a non-isolated process.
+ */
+ // SuppressLint is needed because the parent Context class does not specify the nullability of
+ // the parameter filename. If we remove the @NonNull annotation, the linter will complain about
+ // MissingNullability
+ @Override
+ public @NonNull FileInputStream openFileInput(
+ @SuppressLint("InvalidNullabilityOverride") @NonNull String fileName)
+ throws FileNotFoundException {
+ if (fileName == null) {
+ throw new IllegalArgumentException("filename cannot be null");
+ }
+ try {
+ if (mWearableSensingCallback == null) {
+ throw new IllegalStateException(
+ "Cannot open file from WearableSensingService. WearableSensingCallback is"
+ + " not available.");
+ }
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mWearableSensingCallback.openFile(fileName, future);
+ ParcelFileDescriptor pfd =
+ future.get(OPEN_FILE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ if (pfd == null) {
+ throw new FileNotFoundException(
+ TextUtils.formatSimple(
+ "File %s not found or unable to be opened in read-only mode.",
+ fileName));
+ }
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | TimeoutException e) {
+ throw (FileNotFoundException)
+ new FileNotFoundException("Cannot open file due to remote service failure")
+ .initCause(e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw (FileNotFoundException)
+ new FileNotFoundException("Interrupted when opening a file.").initCause(e);
+ }
+ }
+
@NonNull
private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) {
Integer[] intArray = new Integer[integerSet.length];
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 559fa96d0198..a8a0c5b8bdf7 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -53,6 +53,19 @@ flag {
}
flag {
+ name: "complete_font_load_in_system_services_ready"
+ namespace: "text"
+ description: "Fix to ensure that font loading is complete on system-services-ready boot phase."
+ # Make read only, as font loading is in the critical boot path which happens before the read-write
+ # flags propagate to the device.
+ is_fixed_read_only: true
+ bug: "327941215"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "phrase_strict_fallback"
namespace: "text"
description: "Feature flag for automatic fallback from phrase based line break to strict line break."
@@ -147,3 +160,14 @@ flag {
description: "Feature flag for showing error message when user tries stylus handwriting on a text field which doesn't support it"
bug: "297962571"
}
+
+flag {
+ name: "fix_font_update_failure"
+ namespace: "text"
+ description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
+ is_fixed_read_only: true
+ bug: "331717791"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b30002228d54..6caf4d6ff992 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -196,11 +196,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
if (!super.setControl(control, showTypes, hideTypes)) {
return false;
}
- final boolean hasLeash = control != null && control.getLeash() != null;
- if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) {
+ if (control == null && !mIsRequestedVisibleAwaitingLeash) {
mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
removeSurface();
}
+ final boolean hasLeash = control != null && control.getLeash() != null;
if (hasLeash) {
mIsRequestedVisibleAwaitingLeash = false;
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 90aafbdb164d..b52003f437da 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -31,7 +31,6 @@ import static android.view.WindowInsets.Type.ime;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
@@ -69,7 +68,6 @@ import android.view.inputmethod.ImeTracker.InputMethodJankContext;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.util.function.TriFunction;
@@ -142,10 +140,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
*/
@Appearance int getSystemBarsAppearance();
- default boolean isSystemBarsAppearanceControlled() {
- return false;
- }
-
/**
* @see WindowInsetsController#setSystemBarsBehavior
*/
@@ -156,10 +150,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
*/
@Behavior int getSystemBarsBehavior();
- default boolean isSystemBarsBehaviorControlled() {
- return false;
- }
-
/**
* Releases a surface and ensure that this is done after {@link #applySurfaceParams} has
* finished applying params.
@@ -387,16 +377,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final WindowInsetsAnimationControlListener mLoggingListener;
private final InputMethodJankContext mInputMethodJankContext;
- private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
- new ThreadLocal<AnimationHandler>() {
- @Override
- protected AnimationHandler initialValue() {
- AnimationHandler handler = new AnimationHandler();
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- return handler;
- }
- };
-
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
@@ -478,9 +458,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
ImeTracker.forJank().onFinishAnimation(getAnimationType());
}
});
- if (!mHasAnimationCallbacks) {
- mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
- }
mAnimator.start();
}
@@ -672,6 +649,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private int mImeCaptionBarInsetsHeight = 0;
private boolean mAnimationsDisabled;
private boolean mCompatSysUiVisibilityStaled;
+ private @Appearance int mAppearanceControlled;
+ private @Appearance int mAppearanceFromResource;
+ private boolean mBehaviorControlled;
private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
@@ -1884,20 +1864,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Override
public void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask) {
+ mAppearanceControlled |= mask;
mHost.setSystemBarsAppearance(appearance, mask);
}
@Override
- public @Appearance int getSystemBarsAppearance() {
- @Appearance int appearance = mHost.getSystemBarsAppearance();
+ public void setSystemBarsAppearanceFromResource(@Appearance int appearance,
+ @Appearance int mask) {
+ mAppearanceFromResource = (mAppearanceFromResource & ~mask) | (appearance & mask);
+
+ // Don't change the flags which are already controlled by setSystemBarsAppearance.
+ mHost.setSystemBarsAppearance(appearance, mask & ~mAppearanceControlled);
+ }
+ @Override
+ public @Appearance int getSystemBarsAppearance() {
// We only return the requested appearance, not the implied one.
- appearance &= ~APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
- if (!mHost.isSystemBarsAppearanceControlled()) {
- appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
- }
+ return (mHost.getSystemBarsAppearance() & mAppearanceControlled)
+ | (mAppearanceFromResource & ~mAppearanceControlled);
+ }
- return appearance;
+ public @Appearance int getAppearanceControlled() {
+ return mAppearanceControlled;
}
@Override
@@ -1949,18 +1937,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Override
public void setSystemBarsBehavior(@Behavior int behavior) {
+ mBehaviorControlled = true;
mHost.setSystemBarsBehavior(behavior);
}
@Override
public @Behavior int getSystemBarsBehavior() {
- if (!mHost.isSystemBarsBehaviorControlled()) {
+ if (!mBehaviorControlled) {
// We only return the requested behavior, not the implied one.
- return 0;
+ return BEHAVIOR_DEFAULT;
}
return mHost.getSystemBarsBehavior();
}
+ public boolean isBehaviorControlled() {
+ return mBehaviorControlled;
+ }
+
@Override
public void setAnimationsDisabled(boolean disable) {
mAnimationsDisabled = disable;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0ae3e598f8b9..56a24e4705b7 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3783,6 +3783,13 @@ public final class MotionEvent extends InputEvent implements Parcelable {
throw new IllegalArgumentException(
"idBits must contain at least one pointer from this motion event");
}
+ final int currentBits = getPointerIdBits();
+ if ((currentBits & idBits) != idBits) {
+ throw new IllegalArgumentException(
+ "idBits must be a non-empty subset of the pointer IDs from this MotionEvent, "
+ + "got idBits: "
+ + String.format("0x%x", idBits) + " for " + this);
+ }
MotionEvent event = obtain();
event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
return event;
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index a4cbc52416b3..00a58063f078 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -37,6 +37,8 @@ public class PendingInsetsController implements WindowInsetsController {
private final ArrayList<PendingRequest> mRequests = new ArrayList<>();
private @Appearance int mAppearance;
private @Appearance int mAppearanceMask;
+ private @Appearance int mAppearanceFromResource;
+ private @Appearance int mAppearanceFromResourceMask;
private @Behavior int mBehavior = KEEP_BEHAVIOR;
private boolean mAnimationsDisabled;
private final InsetsState mDummyState = new InsetsState();
@@ -79,11 +81,21 @@ public class PendingInsetsController implements WindowInsetsController {
}
@Override
+ public void setSystemBarsAppearanceFromResource(int appearance, int mask) {
+ if (mReplayedInsetsController != null) {
+ mReplayedInsetsController.setSystemBarsAppearanceFromResource(appearance, mask);
+ } else {
+ mAppearanceFromResource = (mAppearanceFromResource & ~mask) | (appearance & mask);
+ mAppearanceFromResourceMask |= mask;
+ }
+ }
+
+ @Override
public int getSystemBarsAppearance() {
if (mReplayedInsetsController != null) {
return mReplayedInsetsController.getSystemBarsAppearance();
}
- return mAppearance;
+ return mAppearance | (mAppearanceFromResource & ~mAppearanceMask);
}
@Override
@@ -171,6 +183,10 @@ public class PendingInsetsController implements WindowInsetsController {
if (mAppearanceMask != 0) {
controller.setSystemBarsAppearance(mAppearance, mAppearanceMask);
}
+ if (mAppearanceFromResourceMask != 0) {
+ controller.setSystemBarsAppearanceFromResource(
+ mAppearanceFromResource, mAppearanceFromResourceMask);
+ }
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
@@ -199,6 +215,8 @@ public class PendingInsetsController implements WindowInsetsController {
mBehavior = KEEP_BEHAVIOR;
mAppearance = 0;
mAppearanceMask = 0;
+ mAppearanceFromResource = 0;
+ mAppearanceFromResourceMask = 0;
mAnimationsDisabled = false;
mLoggingListener = null;
mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6c6e8b247886..188ad8f7e47c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -203,9 +203,7 @@ public class Surface implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
- value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
- FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
- FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
+ value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
public @interface FrameRateCompatibility {}
// From native_window.h. Keep these in sync.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cfdf8fab05c2..1cd7d349a9af 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1272,7 +1272,7 @@ public final class SurfaceControl implements Parcelable {
* surface has no buffer or crop, the surface is boundless and only constrained
* by the size of its parent bounds.
*
- * @param session The surface session, must not be null.
+ * @param session The surface session.
* @param name The surface name, must not be null.
* @param w The surface initial width.
* @param h The surface initial height.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bd8e9c6b4bf8..eb7de93b83f3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -226,6 +226,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -3334,16 +3335,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000;
/**
- * Live region mode specifying that accessibility services should announce
- * changes to this view.
+ * Live region mode specifying that accessibility services should notify users of changes to
+ * this view.
* <p>
* Use with {@link #setAccessibilityLiveRegion(int)}.
*/
public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
/**
- * Live region mode specifying that accessibility services should interrupt
- * ongoing speech to immediately announce changes to this view.
+ * Live region mode specifying that accessibility services should immediately notify users of
+ * changes to this view. For example, a screen reader may interrupt ongoing speech to
+ * immediately announce these changes.
* <p>
* Use with {@link #setAccessibilityLiveRegion(int)}.
*/
@@ -8797,14 +8799,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* <p>
* When transitioning from one Activity to another, instead of using
- * setAccessibilityPaneTitle(), set a descriptive title for its window by using android:label
- * for the matching <activity> entry in your application’s manifest or updating the title at
- * runtime with{@link android.app.Activity#setTitle(CharSequence)}.
+ * {@code setAccessibilityPaneTitle()}, set a descriptive title for its window by using
+ * {@code android:label}
+ * for the matching Activity entry in your application's manifest or updating the title at
+ * runtime with {@link android.app.Activity#setTitle(CharSequence)}.
*
* <p>
+ * <aside>
* <b>Note:</b> Use
* {@link androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}
- * for backwards-compatibility. </aside>
+ * for backwards-compatibility.
+ * </aside>
* @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
* View is not a pane.
*
@@ -8912,7 +8917,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* They should not need to specify what exactly is announced to users.
*
* <p>
- * In general, only announce transitions and don’t generate a confirmation message for simple
+ * In general, only announce transitions and don't generate a confirmation message for simple
* actions like a button press. Label your controls concisely and precisely instead, and for
* significant UI changes like window changes, use
* {@link android.app.Activity#setTitle(CharSequence)} and
@@ -15305,33 +15310,56 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* to the view's content description or text, or to the content descriptions
* or text of the view's children (where applicable).
* <p>
- * To indicate that the user should be notified of changes, use
- * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. Announcements from this region are queued and
- * do not disrupt ongoing speech.
+ * Different priority levels are available:
+ * <ul>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}:
+ * Indicates that updates to the region should be presented to the user. Suitable in most
+ * cases for prominent updates within app content that don't require the user's immediate
+ * attention.
+ * </li>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}: Indicates that updates to the region have
+ * the highest priority and should be presented to the user immediately. This may result
+ * in disruptive notifications from an accessibility service, which may potentially
+ * interrupt other feedback or user actions, so it should generally be used only for
+ * critical, time-sensitive information.
+ * </li>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_NONE}: Disables change announcements (the default for
+ * most views).
+ * </li>
+ * </ul>
* <p>
- * For example, selecting an option in a dropdown menu may update a panel below with the updated
- * content. This panel may be marked as a live region with
- * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change.
+ * Examples:
+ * <ul>
+ * <li>
+ * Selecting an option in a dropdown menu updates a panel below with the updated
+ * content. This panel may be marked as a live region with
+ * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change. A screen
+ * reader may queue changes as announcements that don't disrupt ongoing speech.
+ * </li>
+ * <li>
+ * An emergency alert may be marked with {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
+ * to immediately inform users of the emergency.
+ * </li>
+ * </ul>
* <p>
- * For notifying users about errors, such as in a login screen with text that displays an
- * "incorrect password" notification, that view should send an AccessibilityEvent of type
+ * For error notifications, like an "incorrect password" warning in a login screen, views
+ * should send a {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ * {@code AccessibilityEvent} with a content change type
* {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
- * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
- * error-setting methods that support accessibility automatically. For example, instead of
- * explicitly sending this event when using a TextView, use
- * {@link android.widget.TextView#setError(CharSequence)}.
- * <p>
- * To disable change notifications for this view, use
- * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
- * mode for most views.
+ * {@link AccessibilityNodeInfo#setError(CharSequence)}. Custom widgets should provide
+ * error-setting methods that support accessibility. For example, use
+ * {@link android.widget.TextView#setError(CharSequence)} instead of explicitly sending events.
* <p>
- * If the view's changes should interrupt ongoing speech and notify the user
- * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. This may result in disruptive
- * announcements from an accessibility service, so it should generally be used only to convey
- * information that is time-sensitive or critical for use of the application. Examples may
- * include an incoming call or an emergency alert.
+ * Don't use live regions for frequently-updating UI elements (e.g., progress bars), as this can
+ * overwhelm the user with feedback from accessibility services. If necessary, use
+ * {@link AccessibilityNodeInfo#setMinDurationBetweenContentChanges(Duration)} to throttle
+ * feedback and reduce disruptions.
* <p>
- * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
+ * <aside><b>Note:</b> Use
+ * {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
* for backwards-compatibility. </aside>
*
* @param mode The live region mode for this view, one of:
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2e8f2bee3074..c5a4d677e70e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -34,13 +34,13 @@ 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.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -69,11 +69,10 @@ import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
-import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.Appearance;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
@@ -85,8 +84,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
@@ -107,11 +104,13 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.accessibility.Flags.fixMergedContentChangeEventV2;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -1155,6 +1154,7 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
+ private static final boolean sToolkitFrameRateViewEnablingReadOnlyFlagValue;
private static boolean sToolkitFrameRateVelocityMappingReadOnlyFlagValue =
toolkitFrameRateVelocityMappingReadOnly();;
@@ -1164,6 +1164,8 @@ public final class ViewRootImpl implements ViewParent,
sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
sToolkitFrameRateFunctionEnablingReadOnlyFlagValue =
toolkitFrameRateFunctionEnablingReadOnly();
+ sToolkitFrameRateViewEnablingReadOnlyFlagValue =
+ toolkitFrameRateViewEnablingReadOnly();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1522,7 +1524,9 @@ public final class ViewRootImpl implements ViewParent,
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
- adjustLayoutParamsForCompatibility(mWindowAttributes);
+ adjustLayoutParamsForCompatibility(mWindowAttributes,
+ mInsetsController.getAppearanceControlled(),
+ mInsetsController.isBehaviorControlled());
controlInsetsForCompatibility(mWindowAttributes);
Rect attachedFrame = new Rect();
@@ -2038,8 +2042,6 @@ public final class ViewRootImpl implements ViewParent,
// Preserve appearance and behavior.
final int appearance = mWindowAttributes.insetsFlags.appearance;
final int behavior = mWindowAttributes.insetsFlags.behavior;
- final int appearanceAndBehaviorPrivateFlags = mWindowAttributes.privateFlags
- & (PRIVATE_FLAG_APPEARANCE_CONTROLLED | PRIVATE_FLAG_BEHAVIOR_CONTROLLED);
final int changes = mWindowAttributes.copyFrom(attrs);
if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
@@ -2062,7 +2064,6 @@ public final class ViewRootImpl implements ViewParent,
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
mWindowAttributes.insetsFlags.appearance = appearance;
mWindowAttributes.insetsFlags.behavior = behavior;
- mWindowAttributes.privateFlags |= appearanceAndBehaviorPrivateFlags;
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
@@ -2627,8 +2628,10 @@ public final class ViewRootImpl implements ViewParent,
// no longer needed if the dVRR feature is disabled.
if (shouldEnableDvrr()) {
try {
- mFrameRateTransaction.setFrameRateSelectionStrategy(sc,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRateSelectionStrategy(sc,
sc.FRAME_RATE_SELECTION_STRATEGY_SELF).applyAsyncUnsafe();
+ }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate selection strategy ", e);
}
@@ -2916,26 +2919,35 @@ public final class ViewRootImpl implements ViewParent,
}
@VisibleForTesting
- public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams) {
+ public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams,
+ @Appearance int appearanceControlled, boolean behaviorControlled) {
final int sysUiVis = inOutParams.systemUiVisibility | inOutParams.subtreeSystemUiVisibility;
final int flags = inOutParams.flags;
final int type = inOutParams.type;
final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
- if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
- inOutParams.insetsFlags.appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
- if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
- inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS;
- }
- if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) {
- inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_STATUS_BARS;
- }
- if ((sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0) {
- inOutParams.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
- }
+ @Appearance int appearance = inOutParams.insetsFlags.appearance;
+ if ((appearanceControlled & APPEARANCE_LOW_PROFILE_BARS) == 0) {
+ appearance &= ~APPEARANCE_LOW_PROFILE_BARS;
+ appearance |= (sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0
+ ? APPEARANCE_LOW_PROFILE_BARS
+ : 0;
+ }
+ if ((appearanceControlled & APPEARANCE_LIGHT_STATUS_BARS) == 0) {
+ appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
+ appearance |= (sysUiVis & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
+ ? APPEARANCE_LIGHT_STATUS_BARS
+ : 0;
+ }
+ if ((appearanceControlled & APPEARANCE_LIGHT_NAVIGATION_BARS) == 0) {
+ appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
+ appearance |= (sysUiVis & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
+ ? APPEARANCE_LIGHT_NAVIGATION_BARS
+ : 0;
}
+ inOutParams.insetsFlags.appearance = appearance;
- if ((inOutParams.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) {
+ if (!behaviorControlled) {
if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
|| (flags & FLAG_FULLSCREEN) != 0) {
inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -3485,7 +3497,9 @@ public final class ViewRootImpl implements ViewParent,
&& !PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
- adjustLayoutParamsForCompatibility(params);
+ adjustLayoutParamsForCompatibility(params,
+ mInsetsController.getAppearanceControlled(),
+ mInsetsController.isBehaviorControlled());
controlInsetsForCompatibility(params);
if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
@@ -6370,6 +6384,12 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_KEEP_CLEAR_RECTS_CHANGED";
case MSG_REFRESH_POINTER_ICON:
return "MSG_REFRESH_POINTER_ICON";
+ case MSG_TOUCH_BOOST_TIMEOUT:
+ return "MSG_TOUCH_BOOST_TIMEOUT";
+ case MSG_CHECK_INVALIDATION_IDLE:
+ return "MSG_CHECK_INVALIDATION_IDLE";
+ case MSG_FRAME_RATE_SETTING:
+ return "MSG_FRAME_RATE_SETTING";
}
return super.getMessageName(message);
}
@@ -9567,6 +9587,8 @@ public final class ViewRootImpl implements ViewParent,
}
mRemoved = true;
mOnBackInvokedDispatcher.detachFromWindow();
+ removeVrrMessages();
+
if (mAdded) {
dispatchDetachedFromWindow();
}
@@ -12528,8 +12550,10 @@ public final class ViewRootImpl implements ViewParent,
+ category + ", reason " + reason + ", "
+ sourceView);
}
- mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
+ }
mLastPreferredFrameRateCategory = frameRateCategory;
}
} catch (Exception e) {
@@ -12587,8 +12611,10 @@ public final class ViewRootImpl implements ViewParent,
+ preferredFrameRate + " compatibility "
+ mFrameRateCompatibility);
}
- mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
mFrameRateCompatibility).applyAsyncUnsafe();
+ }
mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
@@ -12816,7 +12842,7 @@ public final class ViewRootImpl implements ViewParent,
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ if (sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
}
return false;
@@ -12831,4 +12857,10 @@ public final class ViewRootImpl implements ViewParent,
mHasIdledMessage = true;
}
}
+
+ private void removeVrrMessages() {
+ mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
+ mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ }
}
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 421414175434..b66c59aae899 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -17,9 +17,6 @@
package android.view;
import static android.view.InsetsController.DEBUG;
-import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import android.annotation.NonNull;
import android.content.Context;
@@ -174,9 +171,6 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
@Override
public void setSystemBarsAppearance(int appearance, int mask) {
- if ((mask & COMPATIBLE_APPEARANCE_FLAGS) != 0) {
- mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
- }
final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags;
final int newAppearance = (insetsFlags.appearance & ~mask) | (appearance & mask);
if (insetsFlags.appearance != newAppearance) {
@@ -192,13 +186,7 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
}
@Override
- public boolean isSystemBarsAppearanceControlled() {
- return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) != 0;
- }
-
- @Override
public void setSystemBarsBehavior(int behavior) {
- mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior;
mViewRoot.mWindowAttributesChanged = true;
@@ -212,11 +200,6 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
}
@Override
- public boolean isSystemBarsBehaviorControlled() {
- return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) != 0;
- }
-
- @Override
public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
// At the time we receive new leashes (e.g. InsetsSourceConsumer is processing
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 7601ffab6430..1ffffb303e71 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -101,14 +101,6 @@ public interface WindowInsetsController {
int APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS = 1 << 9;
/**
- * Appearance flags that can be implied from system UI flags.
- * @hide
- */
- int COMPATIBLE_APPEARANCE_FLAGS = APPEARANCE_LOW_PROFILE_BARS
- | APPEARANCE_LIGHT_STATUS_BARS
- | APPEARANCE_LIGHT_NAVIGATION_BARS;
-
- /**
* Determines the appearance of system bars.
* @hide
*/
@@ -271,10 +263,23 @@ public interface WindowInsetsController {
void setSystemBarsAppearance(@Appearance int appearance, @Appearance int mask);
/**
+ * Similar to {@link #setSystemBarsAppearance} but the given flag will only take effect when it
+ * is not controlled by {@link #setSystemBarsAppearance}.
+ *
+ * @see WindowInsetsController#getSystemBarsAppearance()
+ * @see android.R.attr#windowLightStatusBar
+ * @see android.R.attr#windowLightNavigationBar
+ * @hide
+ */
+ void setSystemBarsAppearanceFromResource(@Appearance int appearance, @Appearance int mask);
+
+ /**
* Retrieves the requested appearance of system bars.
*
* @return The requested bitmask of system bar appearance controlled by this window.
* @see #setSystemBarsAppearance(int, int)
+ * @see android.R.attr#windowLightStatusBar
+ * @see android.R.attr#windowLightNavigationBar
*/
@Appearance int getSystemBarsAppearance();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 75c063d2d80a..59cb45019e83 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3440,20 +3440,6 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
/**
- * Flag to indicate that the window is controlling the appearance of system bars. So we
- * don't need to adjust it by reading its system UI flags for compatibility.
- * @hide
- */
- public static final int PRIVATE_FLAG_APPEARANCE_CONTROLLED = 1 << 26;
-
- /**
- * Flag to indicate that the window is controlling the behavior of system bars. So we don't
- * need to adjust it by reading its window flags or system UI flags for compatibility.
- * @hide
- */
- public static final int PRIVATE_FLAG_BEHAVIOR_CONTROLLED = 1 << 27;
-
- /**
* Flag to indicate that the window is controlling how it fits window insets on its own.
* So we don't need to adjust its attributes for fitting window insets.
* @hide
@@ -3524,8 +3510,6 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_NOT_MAGNIFIABLE,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
PRIVATE_FLAG_CONSUME_IME_INSETS,
- PRIVATE_FLAG_APPEARANCE_CONTROLLED,
- PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
PRIVATE_FLAG_TRUSTED_OVERLAY,
PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
@@ -3626,14 +3610,6 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
name = "CONSUME_IME_INSETS"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
- equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
- name = "APPEARANCE_CONTROLLED"),
- @ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
- equals = PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
- name = "BEHAVIOR_CONTROLLED"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
name = "FIT_INSETS_CONTROLLED"),
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 03ba8aedb730..a5ba294d6a19 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -159,7 +159,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* <p> To avoid disconnected trees, this flag will also prefetch the parent. Siblings will be
* prefetched before descendants.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_SIBLINGS = 1 << 1;
@@ -171,7 +171,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 1 << 2;
@@ -181,7 +181,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 1 << 3;
@@ -191,7 +191,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 1 << 4;
@@ -199,7 +199,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* Prefetching flag that specifies prefetching should not be interrupted by a request to
* retrieve a node or perform an action on a node.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 1 << 5;
@@ -1295,6 +1295,8 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Get the child at given index.
*
+ * <p>
+ * See {@link #getParent(int)} for a description of prefetching.
* @param index The child index.
* @param prefetchingStrategy the prefetching strategy.
* @return The child node.
@@ -1302,7 +1304,6 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
* calling {@link #setQueryFromAppProcessEnabled}.
*
- * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
*/
@Nullable
public AccessibilityNodeInfo getChild(int index, @PrefetchingStrategy int prefetchingStrategy) {
@@ -1903,8 +1904,13 @@ public class AccessibilityNodeInfo implements Parcelable {
* Accessibility service will throttle those content change events and only handle one event
* per minute for that view.
* </p>
+ * <p>
+ * Example UI elements that frequently update and may benefit from a duration are progress bars,
+ * timers, and stopwatches.
+ * </p>
*
- * @see AccessibilityEvent#getContentChangeTypes for all content change types.
+ * @see AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
+ * @see AccessibilityEvent#getContentChangeTypes
* @param duration the minimum duration between content change events.
* Negative duration would be treated as zero.
*/
@@ -3954,7 +3960,7 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Returns the container title.
*
- * @see #setContainerTitle for details.
+ * @see #setContainerTitle
*/
@Nullable
public CharSequence getContainerTitle() {
@@ -5187,8 +5193,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* <p>The node that is focused should return {@code true} for
* {@link AccessibilityNodeInfo#isFocused()}.
*
- * @see #ACTION_ACCESSIBILITY_FOCUS for the difference between system and accessibility
- * focus.
+ * See {@link #ACTION_ACCESSIBILITY_FOCUS} for the difference between system and
+ * accessibility focus.
*/
public static final AccessibilityAction ACTION_FOCUS =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 80b23969ed73..8174da69becb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1153,6 +1153,9 @@ public final class InputMethodManager {
}
final boolean startInput;
synchronized (mH) {
+ if (reason == UnbindReason.DISCONNECT_IME) {
+ mImeDispatcher.clear();
+ }
if (getBindSequenceLocked() != sequence) {
return;
}
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 4c3a290da1d6..d79903bfd06b 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -94,3 +94,15 @@ flag {
bug: "322836622"
is_fixed_read_only: true
}
+
+flag {
+ name: "ctrl_shift_shortcut"
+ namespace: "input_method"
+ description: "Ctrl+Shift shortcut to switch IMEs"
+ bug: "327198899"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 356d059ba5dc..6e43d0f1c6cd 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -69,6 +69,7 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
import java.text.NumberFormat;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Locale;
@@ -139,6 +140,14 @@ import java.util.Locale;
* <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
* if your application uses a light colored theme (a white background).</p>
*
+ * <h4>Accessibility</h4>
+ * <p>
+ * Consider using
+ * {@link AccessibilityNodeInfo#setMinDurationBetweenContentChanges(Duration)} to
+ * convey to accessibility services that changes can be throttled. This may reduce the
+ * frequency of potentially disruptive notifications.
+ * </p>
+ *
* <p><strong>XML attributes</b></strong>
* <p>
* See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 42c2d80ea322..b5bf529fadbd 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -568,7 +568,7 @@ public class ScrollView extends FrameLayout {
handled = pageScroll(View.FOCUS_DOWN);
break;
case KeyEvent.KEYCODE_SPACE:
- pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
+ handled = pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
break;
}
}
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index c12354176ee3..733e3db42fea 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -26,4 +26,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "delay_notification_to_magnification_when_recents_window_to_front_transition"
+ namespace: "accessibility"
+ description: "The flag controls whether the delaying of notification for recents window to-front transition is needed. In accessibilityController other callbacks will decide sending or canceling the delayed notification."
+ bug: "324949652"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 0a4d2534e818..616004b78dba 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -29,3 +29,17 @@ flag {
description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints"
bug: "324377962"
}
+
+flag {
+ name: "enable_desktop_windowing_task_limit"
+ namespace: "lse_desktop_experience"
+ description: "Enables a limit on the number of Tasks shown in Desktop Mode"
+ bug: "332502912"
+}
+
+flag {
+ name: "enable_windowing_edge_drag_resize"
+ namespace: "lse_desktop_experience"
+ description: "Enables edge drag resizing for all input sources"
+ bug: "323383067"
+}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index aa92af228862..150b04e87d97 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -21,4 +21,14 @@ flag {
namespace: "systemui"
description: "Prevent the system from sending consecutive onVisibilityChanged(false) events."
bug: "285631818"
+}
+
+flag {
+ name: "offload_color_extraction"
+ namespace: "systemui"
+ description: "Let ImageWallpaper take care of its wallpaper color extraction, instead of system_server"
+ bug: "328791519"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 4402ac712d42..87c47da16b9a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -9,6 +9,16 @@ flag {
}
flag {
+ name: "wait_for_transition_on_display_switch"
+ namespace: "windowing_frontend"
+ description: "Waits for Shell transition to start before unblocking the screen after display switch"
+ bug: "301420598"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -118,9 +128,28 @@ flag {
}
flag {
+ name: "fifo_priority_for_major_ui_processes"
+ namespace: "windowing_frontend"
+ description: "Use realtime priority for SystemUI and launcher"
+ bug: "288140556"
+ is_fixed_read_only: true
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
bug: "151861875"
is_fixed_read_only: true
+}
+
+flag {
+ name: "keyguard_appear_transition"
+ namespace: "windowing_frontend"
+ description: "Add transition when keyguard appears"
+ bug: "327970608"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 9481dc91bcc4..ddb8ee0df668 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -24,8 +24,10 @@ import static com.android.internal.accessibility.dialog.AccessibilityTargetHelpe
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -329,11 +331,14 @@ public class AccessibilityShortcutController {
warningToast.show();
}
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
private AlertDialog createShortcutWarningDialog(int userId) {
List<AccessibilityTarget> targets = getTargets(mContext, HARDWARE);
if (targets.size() == 0) {
return null;
}
+ final AccessibilityManager am = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext);
// Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
// Put "don't turn on" as the primary action.
@@ -361,32 +366,34 @@ public class AccessibilityShortcutController {
// to the Settings.
final ComponentName configDefaultService =
ComponentName.unflattenFromString(defaultService);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- configDefaultService.flattenToString(),
- userId);
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(configDefaultService.flattenToString()), userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ configDefaultService.flattenToString(),
+ userId);
+ }
}
})
.setPositiveButton(R.string.accessibility_shortcut_off,
(DialogInterface d, int which) -> {
- if (Flags.updateAlwaysOnA11yService()) {
- Set<String> targetServices =
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext,
- HARDWARE,
- userId);
-
+ Set<String> targetServices =
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext,
+ HARDWARE,
+ userId);
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(
+ false, HARDWARE, targetServices, userId);
+ } else {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
userId);
ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
mContext, targetServices, userId);
- } else {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
}
-
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ba1dffcec73f..f0885922e670 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -23,11 +23,14 @@ import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueF
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -35,6 +38,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* Abstract base class for creating various target related to accessibility service, accessibility
* activity, and allowlisting features.
@@ -108,13 +113,21 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
}
}
+ @SuppressLint("MissingPermission")
@Override
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
- if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
+ if (Flags.a11yQsShortcut()) {
+ final AccessibilityManager am =
+ getContext().getSystemService(AccessibilityManager.class);
+ am.enableShortcutsForTargets(
+ isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
} else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ if (isChecked) {
+ optInValueToSettings(getContext(), getShortcutType(), getId());
+ } else {
+ optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ }
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 7831afb8798e..209778808764 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,17 +16,11 @@
package com.android.internal.accessibility.dialog;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
-
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
-import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -53,31 +47,9 @@ public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServ
@Override
public void onCheckedChanged(boolean isChecked) {
+ super.onCheckedChanged(isChecked);
final ComponentName componentName = ComponentName.unflattenFromString(getId());
-
- if (Flags.updateAlwaysOnA11yService()) {
- super.onCheckedChanged(isChecked);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId());
- } else {
- if (!isComponentIdExistingInOtherShortcut()) {
- setAccessibilityServiceState(getContext(), componentName, isChecked);
- }
-
- super.onCheckedChanged(isChecked);
- }
- }
-
- private boolean isComponentIdExistingInOtherShortcut() {
- switch (getShortcutType()) {
- case SOFTWARE:
- return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
- getId());
- case HARDWARE:
- return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
- getId());
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId());
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 753597914782..f9efb5059c09 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -17,17 +17,11 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
-import android.content.ComponentName;
import android.content.Context;
-import android.widget.Toast;
-import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -47,30 +41,10 @@ class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServic
@Override
public void onCheckedChanged(boolean isChecked) {
- switch (getShortcutType()) {
- case SOFTWARE:
- onCheckedFromAccessibilityButton(isChecked);
- return;
- case HARDWARE:
- super.onCheckedChanged(isChecked);
- return;
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
- }
-
- private void onCheckedFromAccessibilityButton(boolean isChecked) {
- setShortcutEnabled(isChecked);
- final ComponentName componentName = ComponentName.unflattenFromString(getId());
- setAccessibilityServiceState(getContext(), componentName, isChecked);
-
- if (!isChecked) {
- optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
-
- final String warningText =
- getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
- getLabel());
- Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+ if (getShortcutType() == HARDWARE) {
+ super.onCheckedChanged(isChecked);
+ } else {
+ throw new IllegalStateException("Unexpected shortcut type");
}
}
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 6bd273bc1aeb..4f5544172edf 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
+import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER;
import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
import android.annotation.Nullable;
@@ -190,7 +191,7 @@ public class IntentForwarderActivity extends Activity {
.thenApplyAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
- callingUserId, targetUserId);
+ callingUserId, targetUserId, false);
// When switching to the personal profile, automatically start the activity
} else if (className.equals(FORWARD_INTENT_TO_PARENT)) {
startActivityAsCaller(newIntent, targetUserId);
@@ -218,7 +219,7 @@ public class IntentForwarderActivity extends Activity {
.thenAcceptAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
- callingUserId, targetUserId);
+ callingUserId, targetUserId, true);
} else {
maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
targetUserId);
@@ -490,7 +491,7 @@ public class IntentForwarderActivity extends Activity {
}
private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className,
- Intent newIntent, int callingUserId, int targetUserId) {
+ Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly) {
// When showing the intent resolver, instead of forwarding to the other profile,
// we launch it in the current user and select the other tab. This fixes b/155874820.
//
@@ -505,6 +506,9 @@ public class IntentForwarderActivity extends Activity {
sanitizeIntent(intentReceived);
intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
+ if (singleTabOnly) {
+ intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
+ }
startActivityAsCaller(intentReceived, null, false, userId);
finish();
}
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
index 9ff05a6589b7..8253aa89b979 100644
--- a/core/java/com/android/internal/compat/Android.bp
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "compat_logging_flags",
package: "com.android.internal.compat.flags",
+ container: "system",
srcs: [
"compat_logging_flags.aconfig",
],
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
index a5c31edde473..4f5162693408 100644
--- a/core/java/com/android/internal/compat/compat_logging_flags.aconfig
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.internal.compat.flags"
+container: "system"
flag {
name: "skip_old_and_disabled_compat_logging"
@@ -6,4 +7,4 @@ flag {
description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
bug: "323949942"
is_fixed_read_only: true
-} \ No newline at end of file
+}
diff --git a/core/java/com/android/internal/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp
index f1d06da98186..53a9be5fdb0e 100644
--- a/core/java/com/android/internal/foldables/Android.bp
+++ b/core/java/com/android/internal/foldables/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "fold_lock_setting_flags",
package: "com.android.internal.foldables.flags",
+ container: "system",
srcs: [
"fold_lock_setting_flags.aconfig",
],
diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
index d73e62373732..03b6a5bcf467 100644
--- a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
+++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.internal.foldables.flags"
+container: "system"
flag {
name: "fold_lock_setting_enabled"
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 02cb53e23bc0..e6a2a6c375f7 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -22,6 +22,8 @@ import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -2628,15 +2630,24 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
false)) {
params.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
}
- final int sysUiVis = decor.getSystemUiVisibility();
- final int statusLightFlag = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- final int statusFlag = a.getBoolean(R.styleable.Window_windowLightStatusBar, false)
- ? statusLightFlag : 0;
- final int navLightFlag = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- final int navFlag = a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)
- ? navLightFlag : 0;
+
+ final boolean lightStatus = a.getBoolean(R.styleable.Window_windowLightStatusBar, false);
+ final boolean lightNav = a.getBoolean(R.styleable.Window_windowLightNavigationBar, false);
+
+ // Here still sets the light bar flags via setSystemUiVisibility (even it is deprecated) to
+ // make the light bar state be able to be read from the legacy method.
decor.setSystemUiVisibility(
- (sysUiVis & ~(statusLightFlag | navLightFlag)) | (statusFlag | navFlag));
+ (decor.getSystemUiVisibility()
+ & ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR))
+ | (lightStatus ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0)
+ | (lightNav ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0));
+
+ decor.getWindowInsetsController().setSystemBarsAppearanceFromResource(
+ (lightStatus ? APPEARANCE_LIGHT_STATUS_BARS : 0)
+ | (lightNav ? APPEARANCE_LIGHT_NAVIGATION_BARS : 0),
+ APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS);
+
if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
@@ -3945,6 +3956,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public int getNavigationBarColor() {
+ if (mEdgeToEdgeEnforced) {
+ return Color.TRANSPARENT;
+ }
return mNavigationBarColor;
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2096ba42080f..d24487412313 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -66,6 +66,7 @@ public class LegacyProtoLogImpl implements IProtoLog {
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final Runnable mCacheUpdater;
private final int mPerChunkSize;
private boolean mProtoLogEnabled;
@@ -73,20 +74,21 @@ public class LegacyProtoLogImpl implements IProtoLog {
private final Object mProtoLogEnabledLock = new Object();
public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups, cacheUpdater);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
- this.mLogGroups = logGroups;
+ mLogGroups = logGroups;
+ mCacheUpdater = cacheUpdater;
}
/**
@@ -285,6 +287,8 @@ public class LegacyProtoLogImpl implements IProtoLog {
return -1;
}
}
+
+ mCacheUpdater.run();
return 0;
}
@@ -399,5 +403,12 @@ public class LegacyProtoLogImpl implements IProtoLog {
public int stopLoggingToLogcat(String[] groups, ILogger logger) {
return setLogging(true /* setTextLogging */, false, logger, groups);
}
+
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ // In legacy logging we just enable an entire group at a time without more granular control,
+ // so we ignore the level argument to this function.
+ return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 561ca2178966..9f3ce8163bf3 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -52,6 +52,7 @@ import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
import android.tracing.perfetto.TracingContext;
+import android.util.ArrayMap;
import android.util.LongArray;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
@@ -70,6 +71,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -83,18 +85,22 @@ public class PerfettoProtoLogImpl implements IProtoLog {
private final AtomicInteger mTracingInstances = new AtomicInteger();
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
- this.mTracingInstances::incrementAndGet,
+ this::onTracingInstanceStart,
this::dumpTransitionTraceConfig,
- this.mTracingInstances::decrementAndGet
+ this::onTracingInstanceStop
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final Runnable mCacheUpdater;
+
+ private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
+ private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
public PerfettoProtoLogImpl(String viewerConfigFilePath,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -102,28 +108,32 @@ public class PerfettoProtoLogImpl implements IProtoLog {
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- }, logGroups);
+ }, logGroups, cacheUpdater);
}
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- TreeMap<String, IProtoLogGroup> logGroups
+ TreeMap<String, IProtoLogGroup> logGroups,
+ Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups,
+ cacheUpdater);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
ProtoLogViewerConfigReader viewerConfigReader,
- TreeMap<String, IProtoLogGroup> logGroups
+ TreeMap<String, IProtoLogGroup> logGroups,
+ Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
mDataSource.register(DataSourceParams.DEFAULTS);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
this.mLogGroups = logGroups;
+ this.mCacheUpdater = cacheUpdater;
}
/**
@@ -494,6 +504,29 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return setTextLogging(false, logger, groups);
}
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal();
+ }
+
+ private LogLevel getLogFromLevel(IProtoLogGroup group) {
+ if (mLogLevelCounts.containsKey(group)) {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ } else {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ }
+
+ return LogLevel.WTF;
+ }
+
/**
* Start logging the stack trace of the when the log message happened for target groups
* @return status code
@@ -521,6 +554,8 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return -1;
}
}
+
+ mCacheUpdater.run();
return 0;
}
@@ -567,6 +602,61 @@ public class PerfettoProtoLogImpl implements IProtoLog {
return -1;
}
+ private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.incrementAndGet();
+
+ final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1);
+
+ final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
+
+ for (String overriddenGroupTag : overriddenGroupTags) {
+ IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
+
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+
+ final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1);
+ }
+
+ mCacheUpdater.run();
+ }
+
+ private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.decrementAndGet();
+
+ final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.get(defaultLogFrom) - 1);
+ if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) {
+ mDefaultLogLevelCounts.remove(defaultLogFrom);
+ }
+
+ final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
+
+ for (String overriddenGroupTag : overriddenGroupTags) {
+ IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
+
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+
+ final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.get(logFromLevel) - 1);
+ if (logLevelsCountsForGroup.get(logFromLevel) <= 0) {
+ logLevelsCountsForGroup.remove(logFromLevel);
+ }
+ if (logLevelsCountsForGroup.isEmpty()) {
+ mLogLevelCounts.remove(group);
+ }
+ }
+
+ mCacheUpdater.run();
+ }
+
static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Slog.i(LOG_TAG, msg);
if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index a2d5e70ee412..e79bf36343db 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -39,16 +39,19 @@ import com.android.internal.protolog.common.LogLevel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
ProtoLogDataSource.IncrementalState> {
- private final Runnable mOnStart;
+ private final Consumer<ProtoLogConfig> mOnStart;
private final Runnable mOnFlush;
- private final Runnable mOnStop;
+ private final Consumer<ProtoLogConfig> mOnStop;
- public ProtoLogDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
+ public ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush,
+ Consumer<ProtoLogConfig> onStop) {
super("android.protolog");
this.mOnStart = onStart;
this.mOnFlush = onFlush;
@@ -138,7 +141,7 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
public boolean clearReported = false;
}
- private static class ProtoLogConfig {
+ public static class ProtoLogConfig {
private final LogLevel mDefaultLogFromLevel;
private final Map<String, GroupConfig> mGroupConfigs;
@@ -151,13 +154,17 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
this.mGroupConfigs = groupConfigs;
}
- private GroupConfig getConfigFor(String groupTag) {
+ public GroupConfig getConfigFor(String groupTag) {
return mGroupConfigs.getOrDefault(groupTag, getDefaultGroupConfig());
}
- private GroupConfig getDefaultGroupConfig() {
+ public GroupConfig getDefaultGroupConfig() {
return new GroupConfig(mDefaultLogFromLevel, false);
}
+
+ public Set<String> getGroupTagsWithOverriddenConfigs() {
+ return mGroupConfigs.keySet();
+ }
}
public static class GroupConfig {
@@ -255,18 +262,18 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
public static class Instance extends DataSourceInstance {
- private final Runnable mOnStart;
+ private final Consumer<ProtoLogConfig> mOnStart;
private final Runnable mOnFlush;
- private final Runnable mOnStop;
+ private final Consumer<ProtoLogConfig> mOnStop;
private final ProtoLogConfig mConfig;
public Instance(
DataSource<Instance, TlsState, IncrementalState> dataSource,
int instanceIdx,
ProtoLogConfig config,
- Runnable onStart,
+ Consumer<ProtoLogConfig> onStart,
Runnable onFlush,
- Runnable onStop
+ Consumer<ProtoLogConfig> onStop
) {
super(dataSource, instanceIdx);
this.mOnStart = onStart;
@@ -277,7 +284,7 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
@Override
public void onStart(StartCallbackArguments args) {
- this.mOnStart.run();
+ this.mOnStart.accept(this.mConfig);
}
@Override
@@ -287,7 +294,7 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
@Override
public void onStop(StopCallbackArguments args) {
- this.mOnStop.run();
+ this.mOnStop.accept(this.mConfig);
}
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 487ae8145546..6d142afce626 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -16,6 +16,7 @@
package com.android.internal.protolog;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.CACHE_UPDATER;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
@@ -49,6 +50,9 @@ public class ProtoLogImpl {
@ProtoLogToolInjected(LOG_GROUPS)
private static TreeMap<String, IProtoLogGroup> sLogGroups;
+ @ProtoLogToolInjected(CACHE_UPDATER)
+ private static Runnable sCacheUpdater;
+
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
@Nullable String messageString,
@@ -94,9 +98,12 @@ public class ProtoLogImpl {
getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
}
- public static boolean isEnabled(IProtoLogGroup group) {
- // TODO: Implement for performance reasons, with optional level parameter?
- return true;
+ /**
+ * Should return true iff we should be logging to either protolog or logcat for this group
+ * and log level.
+ */
+ public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return getSingleInstance().isEnabled(group, level);
}
/**
@@ -105,11 +112,14 @@ public class ProtoLogImpl {
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
+ sServiceInstance = new PerfettoProtoLogImpl(
+ sViewerConfigPath, sLogGroups, sCacheUpdater);
} else {
sServiceInstance = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups, sCacheUpdater);
}
+
+ sCacheUpdater.run();
}
return sServiceInstance;
}
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index c06d14b2075e..f72d9f79958d 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -52,4 +52,12 @@ public interface IProtoLog {
* @return status code
*/
int stopLoggingToLogcat(String[] groups, ILogger logger);
+
+ /**
+ * Should return true iff logging is enabled to ProtoLog or to Logcat for this group and level.
+ * @param group ProtoLog group to check for.
+ * @param level ProtoLog level to check for.
+ * @return If we need to log this group and level to either ProtoLog or Logcat.
+ */
+ boolean isEnabled(IProtoLogGroup group, LogLevel level);
}
diff --git a/core/java/com/android/internal/protolog/common/IProtoLogGroup.java b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
index 4e9686f99f4b..149aa7aa7170 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
@@ -38,6 +38,7 @@ public interface IProtoLogGroup {
/**
* returns true is any logging is enabled for this group.
+ * @deprecated TODO(b/324128613) remove once we migrate fully to Perfetto
*/
default boolean isLogToAny() {
return isLogToLogcat() || isLogToProto();
@@ -50,6 +51,7 @@ public interface IProtoLogGroup {
/**
* set binary logging for this group.
+ * @deprecated TODO(b/324128613) remove once we migrate fully to Perfetto
*/
void setLogToProto(boolean logToProto);
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 18e3f66c4795..8149cd5bbd3b 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -135,7 +135,7 @@ public class ProtoLog {
* @param group Group to check enable status of.
* @return true iff this is being logged.
*/
- public static boolean isEnabled(IProtoLogGroup group) {
+ public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
if (REQUIRE_PROTOLOGTOOL) {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index 17c82d76408e..2d39f3b4e152 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -23,7 +23,11 @@ import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
enum Value {
- VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+ VIEWER_CONFIG_PATH,
+ LEGACY_OUTPUT_FILE_PATH,
+ LEGACY_VIEWER_CONFIG_PATH,
+ LOG_GROUPS,
+ CACHE_UPDATER
}
Value value();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fd4ff29825a3..03b57d07c0a7 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -309,6 +309,7 @@ cc_library_shared_for_libandroid_runtime {
"libdebuggerd_client",
"libutils",
"libbinder",
+ "libbinderdebug",
"libbinder_ndk",
"libui",
"libgraphicsenv",
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 593bdf0812aa..163f32e022e6 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -110,3 +110,6 @@ per-file android_database_SQLite* = file:/SQLITE_OWNERS
# PerformanceHintManager
per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS
+
+# IF Tools
+per-file android_tracing_Perfetto* = file:platform/development:/tools/winscope/OWNERS
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 9593fe584195..3c2dccd451d4 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -16,44 +16,46 @@
#define LOG_TAG "android.os.Debug"
+#include "android_os_Debug.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <assert.h>
+#include <binderdebug/BinderDebug.h>
+#include <bionic/malloc.h>
#include <ctype.h>
+#include <debuggerd/client.h>
+#include <dmabufinfo/dmabuf_sysfs_stats.h>
+#include <dmabufinfo/dmabufinfo.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <log/log.h>
#include <malloc.h>
+#include <meminfo/androidprocheaps.h>
+#include <meminfo/procmeminfo.h>
+#include <meminfo/sysmeminfo.h>
+#include <memtrack/memtrack.h>
+#include <memunreachable/memunreachable.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
+#include <utils/String8.h>
+#include <utils/misc.h>
+#include <vintf/KernelConfigs.h>
#include <iomanip>
#include <string>
#include <vector>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <bionic/malloc.h>
-#include <debuggerd/client.h>
-#include <log/log.h>
-#include <utils/misc.h>
-#include <utils/String8.h>
-
-#include <nativehelper/JNIPlatformHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
-#include <dmabufinfo/dmabuf_sysfs_stats.h>
-#include <dmabufinfo/dmabufinfo.h>
-#include <meminfo/androidprocheaps.h>
-#include <meminfo/procmeminfo.h>
-#include <meminfo/sysmeminfo.h>
-#include <memtrack/memtrack.h>
-#include <memunreachable/memunreachable.h>
-#include <android-base/strings.h>
-#include "android_os_Debug.h"
-#include <vintf/KernelConfigs.h>
namespace android
{
@@ -579,6 +581,15 @@ static bool dumpTraces(JNIEnv* env, jint pid, jstring fileName, jint timeoutSecs
return false;
}
+ std::string binderState;
+ android::status_t status = android::getBinderTransactions(pid, binderState);
+ if (status == android::OK) {
+ if (!android::base::WriteStringToFd(binderState, fd)) {
+ PLOG(ERROR) << "Failed to dump binder state info for pid: " << pid;
+ }
+ } else {
+ PLOG(ERROR) << "Failed to get binder state info for pid: " << pid << " status: " << status;
+ }
int res = dump_backtrace_to_file_timeout(pid, dumpType, timeoutSecs, fd);
if (fdatasync(fd.get()) != 0) {
PLOG(ERROR) << "Failed flushing trace.";
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 7c976b7473f6..b6bf617b40ae 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -36,43 +36,41 @@ static std::string buildFileName(const std::string& locale) {
return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
}
-static std::pair<const uint8_t*, uint32_t> mmapPatternFile(const std::string& locale) {
+static const uint8_t* mmapPatternFile(const std::string& locale) {
const std::string hyFilePath = buildFileName(locale);
const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
- return std::make_pair(nullptr, 0); // Open failed.
+ return nullptr; // Open failed.
}
struct stat st = {};
if (fstat(fd, &st) == -1) { // Unlikely to happen.
close(fd);
- return std::make_pair(nullptr, 0);
+ return nullptr;
}
void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
close(fd);
if (ptr == MAP_FAILED) {
- return std::make_pair(nullptr, 0);
+ return nullptr;
}
- return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size);
+ return reinterpret_cast<const uint8_t*>(ptr);
}
static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
int minSuffix) {
- minikin::addHyphenator(locale,
- minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix,
- locale));
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ nullptr, minPrefix, minSuffix, locale));
}
static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
- auto [ptr, size] = mmapPatternFile(locale);
+ const uint8_t* ptr = mmapPatternFile(locale);
if (ptr == nullptr) {
ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
return;
}
- minikin::addHyphenator(locale,
- minikin::Hyphenator::loadBinary(ptr, size, minPrefix, minSuffix,
- locale));
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ ptr, minPrefix, minSuffix, locale));
}
static void addHyphenatorAlias(const std::string& from, const std::string& to) {
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 1eff5ce8eaa3..25ff853ae7e4 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -213,7 +213,7 @@ void PerfettoDataSource::flushAll() {
PerfettoDataSource::~PerfettoDataSource() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mJavaDataSource);
+ env->DeleteGlobalRef(mJavaDataSource);
}
jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index b900fa60ff70..b51f72dee260 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -11,7 +11,6 @@ zhouwenjie@google.com
# Frameworks
ogunwale@google.com
jjaggi@google.com
-kwekua@google.com
roosa@google.com
per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d4256ca316c2..f74329903690 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7507,16 +7507,6 @@
<permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
android:protectionLevel="signature|privileged|appop" />
- <!-- @SystemApi Required for the privileged assistant apps targeting
- {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive training data from a sandboxed {@link HotwordDetectionService} or
- {@link VisualQueryDetectionService}.
- <p>Protection level: internal|appop
- @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
- @hide -->
- <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
- android:protectionLevel="internal|appop" />
-
<!-- @SystemApi Allows requesting the framework broadcast the
{@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
@hide -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 81c79f23af46..b262ebd3feea 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2400,7 +2400,9 @@
have been requested to be translucent with
{@link android.R.attr#windowTranslucentStatus}.
Corresponds to setting {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_STATUS_BAR} on
- the decor view. -->
+ the decor view and
+ {@link android.view.WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS} on the
+ {@link android.view.WindowInsetsController}. -->
<attr name="windowLightStatusBar" format="boolean" />
<!-- Reference to a drawable to be used as the splash screen content of the window. This
@@ -2422,7 +2424,9 @@
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
Corresponds to setting {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} on
- the decor view. -->
+ the decor view and
+ {@link android.view.WindowInsetsController#APPEARANCE_LIGHT_NAVIGATION_BARS} on the
+ {@link android.view.WindowInsetsController}. -->
<attr name="windowLightNavigationBar" format="boolean" />
<!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index a2a5433eca24..c7d5825733ae 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -175,10 +175,12 @@ class FontScaleConverterFactoryTest {
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.05f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
.isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.2f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
index 66f3bca72aeb..ca9154280a10 100644
--- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -16,6 +16,14 @@
package android.hardware.biometrics;
+import static android.hardware.biometrics.BiometricPrompt.MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.MAX_DESCRIPTION_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptVerticalListContentView.MAX_EACH_ITEM_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptVerticalListContentView.MAX_ITEM_NUMBER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -40,6 +48,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.MockitoRule;
+import java.util.Random;
import java.util.concurrent.Executor;
@@ -83,10 +92,11 @@ public class BiometricPromptTest {
ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
BiometricPrompt.AuthenticationCallback callback =
new BiometricPrompt.AuthenticationCallback() {
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- super.onAuthenticationError(errorCode, errString);
- }};
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }
+ };
mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
mLooper.dispatchAll();
@@ -99,4 +109,112 @@ public class BiometricPromptTest {
verify(mService).cancelAuthentication(any(), anyString(), anyLong());
}
+
+ @Test
+ public void testLogoDescription_null() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new BiometricPrompt.Builder(mContext).setLogoDescription(null)
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "Logo description passed in can not be null or exceed");
+ }
+
+ @Test
+ public void testLogoDescription_charLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new BiometricPrompt.Builder(mContext).setLogoDescription(
+ generateRandomString(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "Logo description passed in can not be null or exceed");
+ }
+
+ @Test
+ public void testMoreOptionsButton_descriptionCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptContentViewWithMoreOptionsButton.Builder().setDescription(
+ generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of description exceeds ");
+ }
+
+ @Test
+ public void testMoreOptionsButton_ExecutorNull() {
+ PromptContentViewWithMoreOptionsButton.Builder builder =
+ new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener(
+ null, null);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ builder::build
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The executor for the listener of more options button on prompt content must be "
+ + "set");
+ }
+
+ @Test
+ public void testMoreOptionsButton_ListenerNull() {
+ PromptContentViewWithMoreOptionsButton.Builder builder =
+ new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener(
+ mExecutor, null);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ builder::build
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The listener of more options button on prompt content must be set");
+ }
+
+ @Test
+ public void testVerticalList_descriptionCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptVerticalListContentView.Builder().setDescription(
+ generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of description exceeds ");
+ }
+
+ @Test
+ public void testVerticalList_itemCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptVerticalListContentView.Builder().addListItem(
+ new PromptContentItemBulletedText(
+ generateRandomString(MAX_EACH_ITEM_CHARACTER_NUMBER + 1)))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of list item exceeds ");
+ }
+
+ @Test
+ public void testVerticalList_itemNumLimit() {
+ PromptVerticalListContentView.Builder builder = new PromptVerticalListContentView.Builder();
+
+ for (int i = 0; i < MAX_ITEM_NUMBER; i++) {
+ builder.addListItem(new PromptContentItemBulletedText(generateRandomString(10)));
+ }
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> builder.addListItem(
+ new PromptContentItemBulletedText(generateRandomString(10)))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The number of list items exceeds ");
+ }
+
+ private String generateRandomString(int charNum) {
+ final Random random = new Random();
+ final StringBuilder longString = new StringBuilder(charNum);
+ for (int j = 0; j < charNum; j++) {
+ longString.append(random.nextInt(10));
+ }
+ return longString.toString();
+ }
}
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index c4c983d24af9..bad048523053 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -26,6 +26,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.graphics.Matrix;
import android.platform.test.annotations.Presubmit;
@@ -47,21 +48,25 @@ import java.util.Set;
public class MotionEventTest {
private static final int ID_SOURCE_MASK = 0x3 << 30;
+ private PointerCoords pointerCoords(float x, float y) {
+ final var coords = new PointerCoords();
+ coords.x = x;
+ coords.y = y;
+ return coords;
+ }
+
+ private PointerProperties fingerProperties(int id) {
+ final var props = new PointerProperties();
+ props.id = id;
+ props.toolType = TOOL_TYPE_FINGER;
+ return props;
+ }
+
@Test
public void testObtainWithDisplayId() {
final int pointerCount = 1;
- PointerProperties[] properties = new PointerProperties[pointerCount];
- final PointerCoords[] coords = new PointerCoords[pointerCount];
- for (int i = 0; i < pointerCount; i++) {
- final PointerCoords c = new PointerCoords();
- c.x = i * 10;
- c.y = i * 20;
- coords[i] = c;
- final PointerProperties p = new PointerProperties();
- p.id = i;
- p.toolType = TOOL_TYPE_FINGER;
- properties[i] = p;
- }
+ final var properties = new PointerProperties[]{fingerProperties(0)};
+ final var coords = new PointerCoords[]{pointerCoords(10, 20)};
int displayId = 2;
MotionEvent motionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN,
@@ -125,18 +130,8 @@ public class MotionEventTest {
@Test
public void testCalculatesCursorPositionForMultiTouchMouseEvents() {
final int pointerCount = 2;
- final PointerProperties[] properties = new PointerProperties[pointerCount];
- final PointerCoords[] coords = new PointerCoords[pointerCount];
-
- for (int i = 0; i < pointerCount; ++i) {
- properties[i] = new PointerProperties();
- properties[i].id = i;
- properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
-
- coords[i] = new PointerCoords();
- coords[i].x = 20 + i * 20;
- coords[i].y = 60 - i * 20;
- }
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
0 /* eventTime */, ACTION_POINTER_DOWN, pointerCount, properties, coords,
@@ -238,4 +233,66 @@ public class MotionEventTest {
assertEquals(10, (int) event.getX());
assertEquals(20, (int) event.getY());
}
+
+ @Test
+ public void testSplit() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ final int idBits = ~0b1 & event.getPointerIdBits();
+ final MotionEvent splitEvent = event.split(idBits);
+ assertEquals(1, splitEvent.getPointerCount());
+ assertEquals(1, splitEvent.getPointerId(0));
+ assertEquals(40, (int) splitEvent.getX());
+ assertEquals(40, (int) splitEvent.getY());
+ }
+
+ @Test
+ public void testSplitFailsWhenNoIdsSpecified() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final MotionEvent splitEvent = event.split(0);
+ fail("Splitting event with id bits 0 should throw: " + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testSplitFailsWhenIdBitsDoNotMatch() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final int idBits = 0b100;
+ final MotionEvent splitEvent = event.split(idBits);
+ fail("Splitting event with id bits that do not match any pointers should throw: "
+ + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 690b35879388..b5b2d0c05daf 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -18,6 +18,7 @@ package android.view;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -154,8 +155,8 @@ public class PendingInsetsControllerTest {
mPendingInsetsController.replayAndAttach(mReplayedController);
mPendingInsetsController.setSystemBarsAppearance(
APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
- verify(mReplayedController).setSystemBarsAppearance(eq(APPEARANCE_LIGHT_STATUS_BARS),
- eq(APPEARANCE_LIGHT_STATUS_BARS));
+ verify(mReplayedController).setSystemBarsAppearance(
+ eq(APPEARANCE_LIGHT_STATUS_BARS), eq(APPEARANCE_LIGHT_STATUS_BARS));
}
@Test
@@ -168,6 +169,24 @@ public class PendingInsetsControllerTest {
}
@Test
+ public void testAppearanceFromResource() {
+ mPendingInsetsController.setSystemBarsAppearanceFromResource(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ verify(mReplayedController).setSystemBarsAppearanceFromResource(
+ eq(APPEARANCE_LIGHT_STATUS_BARS), eq(APPEARANCE_LIGHT_STATUS_BARS));
+ }
+
+ @Test
+ public void testAppearanceFromResource_direct() {
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ mPendingInsetsController.setSystemBarsAppearanceFromResource(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
+ verify(mReplayedController).setSystemBarsAppearanceFromResource(
+ eq(APPEARANCE_LIGHT_STATUS_BARS), eq(APPEARANCE_LIGHT_STATUS_BARS));
+ }
+
+ @Test
public void testAddOnControllableInsetsChangedListener() {
OnControllableInsetsChangedListener listener =
mock(OnControllableInsetsChangedListener.class);
@@ -201,8 +220,10 @@ public class PendingInsetsControllerTest {
public void testReplayTwice() {
mPendingInsetsController.show(systemBars());
mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- mPendingInsetsController.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS,
- APPEARANCE_LIGHT_STATUS_BARS);
+ mPendingInsetsController.setSystemBarsAppearance(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
+ mPendingInsetsController.setSystemBarsAppearanceFromResource(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
mPendingInsetsController.addOnControllableInsetsChangedListener(
(controller, typeMask) -> {});
mPendingInsetsController.replayAndAttach(mReplayedController);
@@ -235,15 +256,29 @@ public class PendingInsetsControllerTest {
public void testDetachReattach() {
mPendingInsetsController.show(systemBars());
mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- mPendingInsetsController.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS,
- APPEARANCE_LIGHT_STATUS_BARS);
+ mPendingInsetsController.setSystemBarsAppearance(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
+ mPendingInsetsController.setSystemBarsAppearanceFromResource(
+ APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
mPendingInsetsController.replayAndAttach(mReplayedController);
mPendingInsetsController.detach();
mPendingInsetsController.show(navigationBars());
+ mPendingInsetsController.setSystemBarsAppearance(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_LIGHT_NAVIGATION_BARS);
+ mPendingInsetsController.setSystemBarsAppearanceFromResource(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_LIGHT_NAVIGATION_BARS);
InsetsController secondController = mock(InsetsController.class);
mPendingInsetsController.replayAndAttach(secondController);
verify(mReplayedController).show(eq(systemBars()));
+ verify(mReplayedController).setSystemBarsAppearance(
+ eq(APPEARANCE_LIGHT_STATUS_BARS), eq(APPEARANCE_LIGHT_STATUS_BARS));
+ verify(mReplayedController).setSystemBarsAppearanceFromResource(
+ eq(APPEARANCE_LIGHT_STATUS_BARS), eq(APPEARANCE_LIGHT_STATUS_BARS));
verify(secondController).show(eq(navigationBars()));
+ verify(secondController).setSystemBarsAppearance(
+ eq(APPEARANCE_LIGHT_NAVIGATION_BARS), eq(APPEARANCE_LIGHT_NAVIGATION_BARS));
+ verify(secondController).setSystemBarsAppearanceFromResource(
+ eq(APPEARANCE_LIGHT_NAVIGATION_BARS), eq(APPEARANCE_LIGHT_NAVIGATION_BARS));
}
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index a86e5681cda6..90ee36e07268 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -154,7 +154,8 @@ public class ViewRootImplTest {
public void adjustLayoutParamsForCompatibility_layoutFullscreen() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
// Type.statusBars() must be removed.
assertEquals(0, attrs.getFitInsetsTypes() & Type.statusBars());
@@ -164,7 +165,8 @@ public class ViewRootImplTest {
public void adjustLayoutParamsForCompatibility_layoutInScreen() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.flags = FLAG_LAYOUT_IN_SCREEN;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
// Type.statusBars() must be removed.
assertEquals(0, attrs.getFitInsetsTypes() & Type.statusBars());
@@ -174,7 +176,8 @@ public class ViewRootImplTest {
public void adjustLayoutParamsForCompatibility_layoutHideNavigation() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
// Type.systemBars() must be removed.
assertEquals(0, attrs.getFitInsetsTypes() & Type.systemBars());
@@ -183,7 +186,8 @@ public class ViewRootImplTest {
@Test
public void adjustLayoutParamsForCompatibility_toast() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_TOAST);
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
assertTrue(attrs.isFitInsetsIgnoringVisibility());
}
@@ -191,7 +195,8 @@ public class ViewRootImplTest {
@Test
public void adjustLayoutParamsForCompatibility_systemAlert() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_SYSTEM_ALERT);
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
assertTrue(attrs.isFitInsetsIgnoringVisibility());
}
@@ -199,7 +204,8 @@ public class ViewRootImplTest {
@Test
public void adjustLayoutParamsForCompatibility_fitSystemBars() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
assertEquals(Type.systemBars(), attrs.getFitInsetsTypes());
}
@@ -208,7 +214,8 @@ public class ViewRootImplTest {
public void adjustLayoutParamsForCompatibility_fitSystemBarsAndIme() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.softInputMode |= SOFT_INPUT_ADJUST_RESIZE;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
assertEquals(Type.systemBars() | Type.ime(), attrs.getFitInsetsTypes());
}
@@ -223,7 +230,8 @@ public class ViewRootImplTest {
attrs.setFitInsetsTypes(types);
attrs.setFitInsetsSides(sides);
attrs.setFitInsetsIgnoringVisibility(fitMaxInsets);
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(
+ attrs, 0 /* appearanceControlled */, false /* behaviorControlled */);
// Fit-insets related fields must not be adjusted due to legacy system UI visibility
// after calling fit-insets related methods.
@@ -234,14 +242,16 @@ public class ViewRootImplTest {
@Test
public void adjustLayoutParamsForCompatibility_noAdjustAppearance() {
- final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+ final InsetsController controller = mViewRootImpl.getInsetsController();
final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
final int appearance = APPEARANCE_OPAQUE_STATUS_BARS;
controller.setSystemBarsAppearance(appearance, 0xffffffff);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LOW_PROFILE
| SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs,
+ controller.getAppearanceControlled(),
+ controller.isBehaviorControlled());
// Appearance must not be adjusted due to legacy system UI visibility after calling
// setSystemBarsAppearance.
@@ -255,12 +265,14 @@ public class ViewRootImplTest {
@Test
public void adjustLayoutParamsForCompatibility_noAdjustBehavior() {
- final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+ final InsetsController controller = mViewRootImpl.getInsetsController();
final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
final int behavior = BEHAVIOR_DEFAULT;
controller.setSystemBarsBehavior(behavior);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs,
+ controller.getAppearanceControlled(),
+ controller.isBehaviorControlled());
// Behavior must not be adjusted due to legacy system UI visibility after calling
// setSystemBarsBehavior.
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 180521ba7b70..5fab1a0f3c8e 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,9 +64,9 @@ import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -91,6 +91,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -98,14 +99,14 @@ import org.mockito.invocation.InvocationOnMock;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -134,6 +135,7 @@ public class AccessibilityShortcutControllerTest {
private @Mock TextToSpeech mTextToSpeech;
private @Mock Voice mVoice;
private @Mock Ringtone mRingtone;
+ private @Captor ArgumentCaptor<List<String>> mListCaptor;
private MockContentResolver mContentResolver;
private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
@@ -418,6 +420,7 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -431,6 +434,29 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
assertThat(
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
).isEmpty();
@@ -440,7 +466,8 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -452,7 +479,8 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -499,6 +527,7 @@ public class AccessibilityShortcutControllerTest {
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -513,15 +542,39 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.SHOWN);
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -536,9 +589,32 @@ public class AccessibilityShortcutControllerTest {
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).isEmpty();
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044ccfb52..a14d8e034b32 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -19,8 +19,10 @@ package com.android.internal.accessibility.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,8 +33,12 @@ import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +53,13 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
/**
* Unit Tests for
@@ -59,9 +68,13 @@ import java.util.Collections;
@RunWith(AndroidJUnit4.class)
public class InvisibleToggleAccessibilityServiceTargetTest {
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock
private IAccessibilityManager mAccessibilityManagerService;
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
private static final String ALWAYS_ON_SERVICE_PACKAGE_LABEL = "always on a11y service";
private static final String ALWAYS_ON_SERVICE_COMPONENT_NAME =
@@ -104,6 +117,32 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(true);
+
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(false);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -116,6 +155,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
enableA11yService(/* enable= */ false);
addShortcutForA11yService(
@@ -128,6 +168,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -140,6 +181,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 826adc39c19a..97147a01b259 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d410d5f5400e..6cf12deea928 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -709,18 +709,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "2959074735946674755": {
- "message": "Trying to update display configuration for system\/invalid process.",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
- "5668810920995272206": {
- "message": "Trying to update display configuration for invalid process, pid=%d",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"-1123414663662718691": {
"message": "setVr2dDisplayId called for: %d",
"level": "DEBUG",
@@ -3469,6 +3457,12 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
+ "257349083882992098": {
+ "message": "updateWallpaperTokens requestedVisibility=%b on keyguardLocked=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"7408402065665963407": {
"message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
"level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f9347ee6ea09..e8b4104a33bb 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,12 +424,14 @@ key 580 APP_SWITCH
key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
+key 585 EMOJI_PICKER
key 656 MACRO_1
key 657 MACRO_2
key 658 MACRO_3
key 659 MACRO_4
# Keys defined by HID usages
+key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp
index ece453d1a70e..f4abd0ab582c 100644
--- a/graphics/java/Android.bp
+++ b/graphics/java/Android.bp
@@ -11,6 +11,7 @@ package {
aconfig_declarations {
name: "framework_graphics_flags",
package: "com.android.graphics.flags",
+ container: "system",
srcs: ["android/framework_graphics.aconfig"],
}
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index 1e41b4d9ed1b..4ab09eb7e9f8 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -1,4 +1,5 @@
package: "com.android.graphics.flags"
+container: "system"
flag {
name: "exact_compute_bounds"
@@ -14,4 +15,4 @@ flag {
namespace: "core_graphics"
description: "Feature flag for YUV image compress to Ultra HDR."
bug: "308978825"
-} \ No newline at end of file
+}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 250362b1e1e3..319f115d7427 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -41,12 +41,15 @@ import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;
import java.io.IOException;
+import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.WeakHashMap;
public final class Bitmap implements Parcelable {
private static final String TAG = "Bitmap";
@@ -120,6 +123,11 @@ public final class Bitmap implements Parcelable {
}
/**
+ * @hide
+ */
+ private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
+
+ /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -162,6 +170,9 @@ public final class Bitmap implements Parcelable {
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
}
registry.registerNativeAllocation(this, nativeBitmap);
+ synchronized (Bitmap.class) {
+ sAllBitmaps.put(this, null);
+ }
}
/**
@@ -1510,6 +1521,86 @@ public final class Bitmap implements Parcelable {
}
/**
+ * @hide
+ */
+ private static final class DumpData {
+ private int count;
+ private int format;
+ private long[] natives;
+ private byte[][] buffers;
+ private int max;
+
+ public DumpData(@NonNull CompressFormat format, int max) {
+ this.max = max;
+ this.format = format.nativeInt;
+ this.natives = new long[max];
+ this.buffers = new byte[max][];
+ this.count = 0;
+ }
+
+ public void add(long nativePtr, byte[] buffer) {
+ natives[count] = nativePtr;
+ buffers[count] = buffer;
+ count = (count >= max) ? max : count + 1;
+ }
+
+ public int size() {
+ return count;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ private static DumpData dumpData = null;
+
+
+ /**
+ * @hide
+ *
+ * Dump all the bitmaps with their contents compressed into dumpData
+ *
+ * @param format format of the compressed image, null to clear dump data
+ */
+ public static void dumpAll(@Nullable String format) {
+ if (format == null) {
+ /* release the dump data */
+ dumpData = null;
+ return;
+ }
+ final CompressFormat fmt;
+ if (format.equals("jpg") || format.equals("jpeg")) {
+ fmt = CompressFormat.JPEG;
+ } else if (format.equals("png")) {
+ fmt = CompressFormat.PNG;
+ } else if (format.equals("webp")) {
+ fmt = CompressFormat.WEBP_LOSSLESS;
+ } else {
+ Log.w(TAG, "No bitmaps dumped: unrecognized format " + format);
+ return;
+ }
+
+ final ArrayList<Bitmap> allBitmaps;
+ synchronized (Bitmap.class) {
+ allBitmaps = new ArrayList<>(sAllBitmaps.size());
+ for (Bitmap bitmap : sAllBitmaps.keySet()) {
+ if (bitmap != null && !bitmap.isRecycled()) {
+ allBitmaps.add(bitmap);
+ }
+ }
+ }
+
+ dumpData = new DumpData(fmt, allBitmaps.size());
+ for (Bitmap bitmap : allBitmaps) {
+ ByteArrayOutputStream bas = new ByteArrayOutputStream();
+ if (bitmap.compress(fmt, 90, bas)) {
+ dumpData.add(bitmap.getNativeInstance(), bas.toByteArray());
+ }
+ }
+ Log.i(TAG, dumpData.size() + "/" + allBitmaps.size() + " bitmaps dumped");
+ }
+
+ /**
* Number of bytes of temp storage we use for communicating between the
* native compressor and the java OutputStream.
*/
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2cac2e150919..2f2215fd51a2 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -17,7 +17,6 @@
package android.security;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.StrictMode;
/**
* This class provides some constants and helper methods related to Android's Keystore service.
@@ -38,17 +37,4 @@ public class KeyStore {
public static KeyStore getInstance() {
return KEY_STORE;
}
-
- /**
- * Add an authentication record to the keystore authorization table.
- *
- * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
- * @return 0 on success, otherwise an error value corresponding to a
- * {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
- */
- public int addAuthToken(byte[] authToken) {
- StrictMode.noteDiskWrite();
-
- return Authorization.addAuthToken(authToken);
- }
}
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/KeyStoreAuthorization.java
index 6404c4bc33d6..14d715f03ae1 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/KeyStoreAuthorization.java
@@ -33,15 +33,21 @@ import android.util.Log;
* @hide This is the client side for IKeystoreAuthorization AIDL.
* It shall only be used by biometric authentication providers and Gatekeeper.
*/
-public class Authorization {
- private static final String TAG = "KeystoreAuthorization";
+public class KeyStoreAuthorization {
+ private static final String TAG = "KeyStoreAuthorization";
public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
+ private static final KeyStoreAuthorization sInstance = new KeyStoreAuthorization();
+
+ public static KeyStoreAuthorization getInstance() {
+ return sInstance;
+ }
+
/**
* @return an instance of IKeystoreAuthorization
*/
- public static IKeystoreAuthorization getService() {
+ private IKeystoreAuthorization getService() {
return IKeystoreAuthorization.Stub.asInterface(
ServiceManager.checkService("android.security.authorization"));
}
@@ -52,7 +58,7 @@ public class Authorization {
* @param authToken created by Android authenticators.
* @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
*/
- public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
+ public int addAuthToken(@NonNull HardwareAuthToken authToken) {
StrictMode.noteSlowCall("addAuthToken");
try {
getService().addAuthToken(authToken);
@@ -70,7 +76,7 @@ public class Authorization {
* @param authToken
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int addAuthToken(@NonNull byte[] authToken) {
+ public int addAuthToken(@NonNull byte[] authToken) {
return addAuthToken(AuthTokenUtils.toHardwareAuthToken(authToken));
}
@@ -82,7 +88,7 @@ public class Authorization {
* is LSKF (or equivalent) and thus has made the synthetic password available
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceUnlocked(int userId, @Nullable byte[] password) {
+ public int onDeviceUnlocked(int userId, @Nullable byte[] password) {
StrictMode.noteDiskWrite();
try {
getService().onDeviceUnlocked(userId, password);
@@ -103,7 +109,7 @@ public class Authorization {
* @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+ public int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
boolean weakUnlockEnabled) {
StrictMode.noteDiskWrite();
try {
@@ -125,14 +131,17 @@ public class Authorization {
* @return the last authentication time or
* {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
*/
- public static long getLastAuthenticationTime(
- long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+ public long getLastAuthTime(long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
try {
return getService().getLastAuthTime(userId, authenticatorTypes);
} catch (RemoteException | NullPointerException e) {
- Log.w(TAG, "Can not connect to keystore", e);
+ Log.w(TAG, "Error getting last auth time: " + e);
return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
} catch (ServiceSpecificException e) {
+ // This is returned when the feature flag test fails in keystore2
+ if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+ throw new UnsupportedOperationException();
+ }
return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
}
}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index b749a06bd516..5c978e21b9bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -210,7 +210,7 @@ android_library {
"androidx.recyclerview_recyclerview",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 1a98ffcea9e7..7f8f57b172ff 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "com_android_wm_shell_flags",
package: "com.android.wm.shell",
+ container: "system",
srcs: [
"multitasking.aconfig",
],
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b61dda4c4e53..7ff204c695f8 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,4 +1,5 @@
package: "com.android.wm.shell"
+container: "system"
flag {
name: "enable_app_pairs"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a541c590575f..c2ba064ac7b6 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,9 +45,6 @@
<!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
<bool name="config_pipEnableResizeForMenu">true</bool>
- <!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
- <fraction name="config_pipShortestEdgePercent">40%</fraction>
-
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">1000</integer>
@@ -91,11 +88,45 @@
16x16
</string>
+ <!-- Default percentages for the PIP size logic.
+ 1. Determine max widths
+ Subtract width of system UI and default padding from the shortest edge of the device.
+ This is the max width.
+ 2. Calculate Default and Mins
+ Default is config_pipSystemPreferredDefaultSizePercent of max-width/height.
+ Min is config_pipSystemPreferredMinimumSizePercent of it. -->
+ <item name="config_pipSystemPreferredDefaultSizePercent" format="float" type="dimen">0.6</item>
+ <item name="config_pipSystemPreferredMinimumSizePercent" format="float" type="dimen">0.5</item>
+ <!-- Default percentages for the PIP size logic when the Display is close to square.
+ This is used instead when the display is square-ish, like fold-ables when unfolded,
+ to make sure that default PiP does not cover the hinge (halfway of the display).
+ 0. Determine if the display is square-ish
+ If min(displayWidth, displayHeight) / max(displayWidth, displayHeight) is greater than
+ config_pipSquareDisplayThresholdForSystemPreferredSize, we use the percent for
+ square display listed below.
+ 1. Determine max widths
+ Subtract width of system UI and default padding from the shortest edge of the device.
+ This is the max width.
+ 2. Calculate Default and Mins
+ Default is config_pipSystemPreferredDefaultSizePercentForSquareDisplay of max-width/height.
+ Min is config_pipSystemPreferredMinimumSizePercentForSquareDisplay of it. -->
+ <item name="config_pipSquareDisplayThresholdForSystemPreferredSize"
+ format="float" type="dimen">0.95</item>
+ <item name="config_pipSystemPreferredDefaultSizePercentForSquareDisplay"
+ format="float" type="dimen">0.5</item>
+ <item name="config_pipSystemPreferredMinimumSizePercentForSquareDisplay"
+ format="float" type="dimen">0.4</item>
+
<!-- The percentage of the screen width to use for the default width or height of
picture-in-picture windows. Regardless of the percent set here, calculated size will never
- be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+ be smaller than @dimen/default_minimal_size_pip_resizable_task.
+ This is used in legacy spec, use config_pipSystemPreferredDefaultSizePercent instead. -->
<item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item>
+ <!-- PiP minimum size, which is a % based off the shorter side of display width and height.
+ This is used in legacy spec, use config_pipSystemPreferredMinimumSizePercent instead. -->
+ <fraction name="config_pipShortestEdgePercent">40%</fraction>
+
<!-- The default aspect ratio for picture-in-picture windows. -->
<item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">
1.777778
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index fa6dd3914ddd..bf654d979856 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -282,6 +282,6 @@
<string name="expand_menu_text">Open Menu</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
- <!-- Maximize menu maximize button string. -->
+ <!-- Maximize menu snap buttons string. -->
<string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 772eae72ac38..c6d46207b119 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -25,6 +25,7 @@ import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.os.RemoteException
+import android.view.Choreographer
import android.view.Display
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
@@ -137,7 +138,7 @@ class CrossActivityBackAnimation @Inject constructor(
enteringTarget!!.taskInfo.taskDescription!!.backgroundColor, transaction
)
ensureScrimLayer()
- transaction.apply()
+ applyTransaction()
}
private fun onGestureProgress(backEvent: BackEvent) {
@@ -150,7 +151,7 @@ class CrossActivityBackAnimation @Inject constructor(
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
currentEnteringRect.offset(0f, yOffset)
applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
- transaction.apply()
+ applyTransaction()
}
private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
@@ -210,7 +211,7 @@ class CrossActivityBackAnimation @Inject constructor(
applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
- transaction.apply()
+ applyTransaction()
}
private fun finishAnimation() {
@@ -226,7 +227,7 @@ class CrossActivityBackAnimation @Inject constructor(
closingTarget = null
background.removeBackground(transaction)
- transaction.apply()
+ applyTransaction()
transformMatrix.reset()
initialTouchPos.set(0f, 0f)
try {
@@ -250,6 +251,11 @@ class CrossActivityBackAnimation @Inject constructor(
.setCornerRadius(leash, cornerRadius)
}
+ private fun applyTransaction() {
+ transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId)
+ transaction.apply()
+ }
+
private fun ensureScrimLayer() {
if (scrimLayer != null) return
val isDarkTheme: Boolean = isDarkMode(context)
@@ -275,7 +281,8 @@ class CrossActivityBackAnimation @Inject constructor(
private fun removeScrimLayer() {
scrimLayer?.let {
if (it.isValid) {
- transaction.remove(it).apply()
+ transaction.remove(it)
+ applyTransaction()
}
}
scrimLayer = null
@@ -287,7 +294,7 @@ class CrossActivityBackAnimation @Inject constructor(
// in case we're still animating an onBackCancelled event, let's remove the finish-
// callback from the progress animator to prevent calling finishAnimation() before
// restarting a new animation
- progressAnimator.removeOnBackCancelledFinishCallback();
+ progressAnimator.removeOnBackCancelledFinishCallback()
startBackAnimation(backMotionEvent)
progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index cae2e80d5320..987001d66219 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -34,6 +34,7 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.RemoteException;
+import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
@@ -192,7 +193,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
- mTransaction.apply();
+ applyTransaction();
mBackground.onBackProgressed(progress);
}
@@ -242,6 +243,11 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
.setCornerRadius(leash, cornerRadius);
}
+ private void applyTransaction() {
+ mTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ mTransaction.apply();
+ }
+
private void finishAnimation() {
if (mEnteringTarget != null) {
mEnteringTarget.leash.release();
@@ -255,8 +261,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
if (mBackground != null) {
mBackground.removeBackground(mTransaction);
}
-
- mTransaction.apply();
+ applyTransaction();
mBackInProgress = false;
mTransformMatrix.reset();
mClosingCurrentRect.setEmpty();
@@ -303,7 +308,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
mBackground.resetStatusBarCustomization();
}
- mTransaction.apply();
+ applyTransaction();
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e3d5b8690993..d2958779c0d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2318,6 +2318,17 @@ public class BubbleController implements ConfigurationChangeListener,
mMainExecutor.execute(() ->
mController.showUserEducation(new Point(positionX, positionY)));
}
+
+ @Override
+ public void setBubbleBarLocation(BubbleBarLocation location) {
+ mMainExecutor.execute(() ->
+ mController.setBubbleBarLocation(location));
+ }
+
+ @Override
+ public void setBubbleBarBounds(Rect bubbleBarBounds) {
+ mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 7a5afec934f5..c9f0f0d61713 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles;
import android.content.Intent;
import android.graphics.Rect;
import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
@@ -42,4 +43,7 @@ interface IBubbles {
oneway void showUserEducation(in int positionX, in int positionY) = 8;
+ oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+
+ oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
} \ No newline at end of file
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 8af4c75b5733..45ad6319bbf8 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
@@ -166,13 +166,8 @@ public class BubbleBarAnimationHelper {
bbev.setTaskViewAlpha(0f);
bbev.setVisibility(VISIBLE);
- // Set the pivot point for the scale, so the view animates out from the bubble bar.
- Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
- mExpandedViewContainerMatrix.setScale(
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarBounds.centerX(),
- bubbleBarBounds.top);
+ setScaleFromBubbleBar(mExpandedViewContainerMatrix,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -214,8 +209,8 @@ public class BubbleBarAnimationHelper {
}
bbev.setScaleX(1f);
bbev.setScaleY(1f);
- mExpandedViewContainerMatrix.setScaleX(1f);
- mExpandedViewContainerMatrix.setScaleY(1f);
+
+ setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f);
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
@@ -240,6 +235,16 @@ public class BubbleBarAnimationHelper {
mExpandedViewAlphaAnimator.reverse();
}
+ private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
+ // Set the pivot point for the scale, so the view animates out from the bubble bar.
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
+ matrix.setScale(
+ scale,
+ scale,
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
+ }
+
/**
* Animate the expanded bubble when it is being dragged
*/
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
index 7ebb7a1c44bf..3c5beeb48806 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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,13 +14,6 @@
* limitations under the License.
*/
-package com.android.aslgen;
+package com.android.wm.shell.common.bubbles;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
- AslgenTests.class,
-})
-public class AllTests {}
+parcelable BubbleBarLocation; \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
index 18c7bdd6d5ba..7eb0f267b312 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.common.pip
import android.content.Context
import android.content.res.Resources
-import android.os.SystemProperties
import android.util.Size
import com.android.wm.shell.R
import java.io.PrintWriter
@@ -36,30 +35,81 @@ class PhoneSizeSpecSource(
private var mOverrideMinSize: Size? = null
- /** Default and minimum percentages for the PIP size logic. */
- private val mDefaultSizePercent: Float
- private val mMinimumSizePercent: Float
+ /**
+ * Default percentages for the PIP size logic.
+ * 1. Determine max widths
+ * Subtract width of system UI and default padding from the shortest edge of the device.
+ * This is the max width.
+ * 2. Calculate Default and Mins
+ * Default is mSystemPreferredDefaultSizePercent of max-width/height.
+ * Min is mSystemPreferredMinimumSizePercent of it.
+ *
+ * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead.
+ */
+ private var mSystemPreferredDefaultSizePercent = 0.6f
+ /** Minimum percentages for the PIP size logic. */
+ private var mSystemPreferredMinimumSizePercent = 0.5f
+
+ /** Threshold to categorize the Display as square, calculated as min(w, h) / max(w, h). */
+ private var mSquareDisplayThresholdForSystemPreferredSize = 0.95f
+ /**
+ * Default percentages for the PIP size logic when the Display is square-ish.
+ * This is used instead when the display is square-ish, like fold-ables when unfolded,
+ * to make sure that default PiP does not cover the hinge (halfway of the display).
+ * 1. Determine max widths
+ * Subtract width of system UI and default padding from the shortest edge of the device.
+ * This is the max width.
+ * 2. Calculate Default and Mins
+ * Default is mSystemPreferredDefaultSizePercent of max-width/height.
+ * Min is mSystemPreferredMinimumSizePercent of it.
+ *
+ * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead.
+ */
+ private var mSystemPreferredDefaultSizePercentForSquareDisplay = 0.5f
+ /** Minimum percentages for the PIP size logic. */
+ private var mSystemPreferredMinimumSizePercentForSquareDisplay = 0.4f
+
+ private val mIsSquareDisplay
+ get() = minOf(pipDisplayLayoutState.displayLayout.width(),
+ pipDisplayLayoutState.displayLayout.height()).toFloat() /
+ maxOf(pipDisplayLayoutState.displayLayout.width(),
+ pipDisplayLayoutState.displayLayout.height()) >
+ mSquareDisplayThresholdForSystemPreferredSize
+ private val mPreferredDefaultSizePercent
+ get() = if (mIsSquareDisplay) mSystemPreferredDefaultSizePercentForSquareDisplay else
+ mSystemPreferredDefaultSizePercent
+
+ private val mPreferredMinimumSizePercent
+ get() = if (mIsSquareDisplay) mSystemPreferredMinimumSizePercentForSquareDisplay else
+ mSystemPreferredMinimumSizePercent
/** Aspect ratio that the PIP size spec logic optimizes for. */
private var mOptimizedAspectRatio = 0f
init {
- mDefaultSizePercent = SystemProperties
- .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
- mMinimumSizePercent = SystemProperties
- .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
-
reloadResources()
}
private fun reloadResources() {
- val res: Resources = context.getResources()
+ val res: Resources = context.resources
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task)
mOverridableMinSize = res.getDimensionPixelSize(
R.dimen.overridable_minimal_size_pip_resizable_task)
+ mSystemPreferredDefaultSizePercent = res.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercent)
+ mSystemPreferredMinimumSizePercent = res.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercent)
+
+ mSquareDisplayThresholdForSystemPreferredSize = res.getFloat(
+ R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize)
+ mSystemPreferredDefaultSizePercentForSquareDisplay = res.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay)
+ mSystemPreferredMinimumSizePercentForSquareDisplay = res.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay)
+
val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
// make sure the optimized aspect ratio is valid with a default value to fall back to
mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
@@ -128,7 +178,7 @@ class PhoneSizeSpecSource(
return minSize
}
val maxSize = getMaxSize(aspectRatio)
- val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+ val defaultWidth = Math.max(Math.round(maxSize.width * mPreferredDefaultSizePercent),
minSize.width)
val defaultHeight = Math.round(defaultWidth / aspectRatio)
return Size(defaultWidth, defaultHeight)
@@ -146,8 +196,8 @@ class PhoneSizeSpecSource(
return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
}
val maxSize = getMaxSize(aspectRatio)
- var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
- var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+ var minWidth = Math.round(maxSize.width * mPreferredMinimumSizePercent)
+ var minHeight = Math.round(maxSize.height * mPreferredMinimumSizePercent)
// make sure the calculated min size is not smaller than the allowed default min size
if (aspectRatio > 1f) {
@@ -244,8 +294,8 @@ class PhoneSizeSpecSource(
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
- pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
- pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+ pw.println(innerPrefix + "mDefaultSizePercent=" + mPreferredDefaultSizePercent)
+ pw.println(innerPrefix + "mMinimumSizePercent=" + mPreferredMinimumSizePercent)
pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index b86e39fca742..4eff3f03670e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -23,19 +23,25 @@ import android.os.Handler;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
@@ -62,6 +68,7 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
+ PipTouchHandler pipTouchHandler,
@NonNull PipScheduler pipScheduler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
@@ -109,4 +116,34 @@ public abstract class Pip2Module {
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
}
+
+
+ @WMSingleton
+ @Provides
+ static PipTouchHandler providePipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+ pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator,
+ pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipMotionHelper providePipMotionHelper(Context context,
+ PipBoundsState pipBoundsState, PhonePipMenuController menuController,
+ PipSnapAlgorithm pipSnapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
+ floatingContentCoordinator, pipPerfHintControllerOptional);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 99a00b8d33c4..120d68120771 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.graphics.Rect
import android.graphics.Region
import android.util.ArrayMap
import android.util.ArraySet
@@ -55,6 +56,8 @@ class DesktopModeTaskRepository {
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
// Track corner/caption regions of desktop tasks, used to determine gesture exclusion
private val desktopExclusionRegions = SparseArray<Region>()
+ // Track last bounds of task before toggled to stable bounds
+ private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -307,6 +310,7 @@ class DesktopModeTaskRepository {
taskId
)
freeformTasksInZOrder.remove(taskId)
+ boundsBeforeMaximizeByTaskId.remove(taskId)
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
"DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
@@ -358,6 +362,20 @@ class DesktopModeTaskRepository {
}
/**
+ * Removes and returns the bounds saved before maximizing the given task.
+ */
+ fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
+ return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
+ }
+
+ /**
+ * Saves the bounds of the given task before maximizing.
+ */
+ fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) {
+ boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ }
+
+ /**
* Check if display with id [displayId] has desktop tasks stashed
*/
fun isStashed(displayId: Int): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c369061810d0..e210ea731f7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -120,7 +120,6 @@ class DesktopTasksController(
private var visualIndicator: DesktopModeVisualIndicator? = null
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
-
private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
t: SurfaceControl.Transaction ->
visualIndicator?.releaseVisualIndicator(t)
@@ -570,7 +569,10 @@ class DesktopTasksController(
}
}
- /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+ /**
+ * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds
+ * if available or the default bounds otherwise.
+ */
fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
@@ -578,11 +580,21 @@ class DesktopTasksController(
displayLayout.getStableBounds(stableBounds)
val destinationBounds = Rect()
if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
- // The desktop task is currently occupying the whole stable bounds, toggle to the
- // default bounds.
- getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ // The desktop task is currently occupying the whole stable bounds. If the bounds
+ // before the task was toggled to stable bounds were saved, toggle the task to those
+ // bounds. Otherwise, toggle to the default bounds.
+ val taskBoundsBeforeMaximize =
+ desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId)
+ if (taskBoundsBeforeMaximize != null) {
+ destinationBounds.set(taskBoundsBeforeMaximize)
+ } else {
+ getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ }
} else {
- // Toggle to the stable bounds.
+ // Save current bounds so that task can be restored back to original bounds if necessary
+ // and toggle to the stable bounds.
+ val taskBounds = taskInfo.configuration.windowConfiguration.bounds
+ desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds)
destinationBounds.set(stableBounds)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index b830a41b6671..0061d03af8e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -369,67 +369,50 @@ class DragToDesktopTransitionHandler(
val startBounds = draggedTaskChange.startAbsBounds
val endBounds = draggedTaskChange.endAbsBounds
- // TODO(b/301106941): Instead of forcing-finishing the animation that scales the
- // surface down and then starting another that scales it back up to the final size,
- // blend the two animations.
- state.dragAnimator.endAnimator()
- // Using [DRAG_FREEFORM_SCALE] to calculate animated width/height is possible because
- // it is known that the animation scale is finished because the animation was
- // force-ended above. This won't be true when the two animations are blended.
- val animStartWidth = (startBounds.width() * DRAG_FREEFORM_SCALE).toInt()
- val animStartHeight = (startBounds.height() * DRAG_FREEFORM_SCALE).toInt()
- // Using end bounds here to find the left/top also assumes the center animation has
- // finished and the surface is placed exactly in the center of the screen which matches
- // the end/default bounds of the now freeform task.
- val animStartLeft = endBounds.centerX() - (animStartWidth / 2)
- val animStartTop = endBounds.centerY() - (animStartHeight / 2)
- val animStartBounds = Rect(
- animStartLeft,
- animStartTop,
- animStartLeft + animStartWidth,
- animStartTop + animStartHeight
+ // Pause any animation that may be currently playing; we will use the relevant
+ // details of that animation here.
+ state.dragAnimator.cancelAnimator()
+ // We still apply scale to task bounds; as we animate the bounds to their
+ // end value, animate scale to 1.
+ val startScale = state.dragAnimator.scale
+ val startPosition = state.dragAnimator.position
+ val unscaledStartWidth = startBounds.width()
+ val unscaledStartHeight = startBounds.height()
+ val unscaledStartBounds = Rect(
+ startPosition.x.toInt(),
+ startPosition.y.toInt(),
+ startPosition.x.toInt() + unscaledStartWidth,
+ startPosition.y.toInt() + unscaledStartHeight
)
-
dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
- t.apply {
- setScale(draggedTaskLeash, 1f, 1f)
- setPosition(
- draggedTaskLeash,
- animStartBounds.left.toFloat(),
- animStartBounds.top.toFloat()
- )
- setWindowCrop(
- draggedTaskLeash,
- animStartBounds.width(),
- animStartBounds.height()
- )
- }
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
- onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t, animStartBounds)
+ onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t,
+ unscaledStartBounds)
finishCallback.onTransitionFinished(null /* wct */)
- // Because the task surface was scaled down during the drag, we must use the animated
- // bounds instead of the [startAbsBounds].
val tx: SurfaceControl.Transaction = transactionSupplier.get()
- ValueAnimator.ofObject(rectEvaluator, animStartBounds, endBounds)
+ ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
.setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
.apply {
addUpdateListener { animator ->
val animBounds = animator.animatedValue as Rect
+ val animFraction = animator.animatedFraction
+ // Progress scale from starting value to 1 as animation plays.
+ val animScale = startScale + animFraction * (1 - startScale)
tx.apply {
- setScale(draggedTaskLeash, 1f, 1f)
- setPosition(
- draggedTaskLeash,
- animBounds.left.toFloat(),
- animBounds.top.toFloat()
- )
+ setScale(draggedTaskLeash, animScale, animScale)
+ setPosition(
+ draggedTaskLeash,
+ animBounds.left.toFloat(),
+ animBounds.top.toFloat()
+ )
setWindowCrop(
- draggedTaskLeash,
- animBounds.width(),
- animBounds.height()
+ draggedTaskLeash,
+ animBounds.width(),
+ animBounds.height()
)
}
onTaskResizeAnimationListener.onBoundsChange(
@@ -493,10 +476,8 @@ class DragToDesktopTransitionHandler(
val draggedTaskChange = state.draggedTaskChange
?: throw IllegalStateException("Expected non-null task change")
val sc = draggedTaskChange.leash
- // TODO(b/301106941): Don't end the animation and start one to scale it back, merge them
- // instead.
- // End the animation that shrinks the window when task is first dragged from fullscreen
- dragToDesktopAnimator.endAnimator()
+ // Pause the animation that shrinks the window when task is first dragged from fullscreen
+ dragToDesktopAnimator.cancelAnimator()
// Then animate the scaled window back to its original bounds.
val x: Float = dragToDesktopAnimator.position.x
val y: Float = dragToDesktopAnimator.position.y
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
new file mode 100644
index 000000000000..e7e797096c0e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import kotlin.Unit;
+
+/**
+ * Handler of all Magnetized Object related code for PiP.
+ */
+public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListener {
+
+ /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private DismissView mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ // Allow dragging the PIP to a location to close it
+ private boolean mEnableDismissDragToEdge;
+
+ private int mTargetSize;
+ private int mDismissAreaHeight;
+ private float mMagneticFieldRadiusPercent = 1f;
+ private WindowInsets mWindowInsets;
+
+ private SurfaceControl mTaskLeash;
+ private boolean mHasDismissTargetSurface;
+
+ private final Context mContext;
+ private final PipMotionHelper mMotionHelper;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final WindowManager mWindowManager;
+ private final ShellExecutor mMainExecutor;
+
+ public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
+ PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipUiEventLogger = pipUiEventLogger;
+ mMotionHelper = motionHelper;
+ mMainExecutor = mainExecutor;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ void init() {
+ Resources res = mContext.getResources();
+ mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ if (mTargetViewContainer != null) {
+ // init can be called multiple times, remove the old one from view hierarchy first.
+ cleanUpDismissTarget();
+ }
+
+ mTargetViewContainer = new DismissView(mContext);
+ DismissViewUtils.setup(mTargetViewContainer);
+ mTargetView = mTargetViewContainer.getCircle();
+ mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ updateMagneticTargetSize();
+ }
+ return windowInsets;
+ });
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagnetizedPip.clearAllTargets();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ updateMagneticTargetSize();
+
+ mMagnetizedPip.setAnimateStuckToTarget(
+ (target, velX, velY, flung, after) -> {
+ if (mEnableDismissDragToEdge) {
+ mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+ }
+ return Unit.INSTANCE;
+ });
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ // Show the dismiss target, in case the initial touch event occurred within
+ // the magnetic field radius.
+ if (mEnableDismissDragToEdge) {
+ showDismissTargetMaybe();
+ }
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+ hideDismissTargetMaybe();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ if (mEnableDismissDragToEdge) {
+ mMainExecutor.executeDelayed(() -> {
+ mMotionHelper.notifyDismissalPending();
+ mMotionHelper.animateDismiss();
+ hideDismissTargetMaybe();
+
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+ }, 0);
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = true;
+ updateDismissTargetLayer();
+ return true;
+ }
+
+ /**
+ * Potentially start consuming future motion events if PiP is currently near the magnetized
+ * object.
+ */
+ public boolean maybeConsumeMotionEvent(MotionEvent ev) {
+ return mMagnetizedPip.maybeConsumeMotionEvent(ev);
+ }
+
+ /**
+ * Update the magnet size.
+ */
+ public void updateMagneticTargetSize() {
+ if (mTargetView == null) {
+ return;
+ }
+ if (mTargetViewContainer != null) {
+ mTargetViewContainer.updateResources();
+ }
+
+ final Resources res = mContext.getResources();
+ mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ // Set the magnetic field radius equal to the target size from the center of the target
+ setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
+ }
+
+ /**
+ * Increase or decrease the field radius of the magnet object, e.g. with larger percent,
+ * PiP will magnetize to the field sooner.
+ */
+ public void setMagneticFieldRadiusPercent(float percent) {
+ mMagneticFieldRadiusPercent = percent;
+ mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize
+ * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ }
+
+ public void setTaskLeash(SurfaceControl taskLeash) {
+ mTaskLeash = taskLeash;
+ }
+
+ private void updateDismissTargetLayer() {
+ if (!mHasDismissTargetSurface || mTaskLeash == null) {
+ // No dismiss target surface, can just return
+ return;
+ }
+
+ final SurfaceControl targetViewLeash =
+ mTargetViewContainer.getViewRootImpl().getSurfaceControl();
+ if (!targetViewLeash.isValid()) {
+ // The surface of mTargetViewContainer is somehow not ready, bail early
+ return;
+ }
+
+ // Put the dismiss target behind the task
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(targetViewLeash, mTaskLeash, -1);
+ t.apply();
+ }
+
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ public void createOrUpdateDismissTarget() {
+ if (mTargetViewContainer.getParent() == null) {
+ mTargetViewContainer.cancelAnimators();
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = false;
+
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+ } else {
+ mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ }
+
+ /** Returns layout params for the dismiss target, using the latest display metrics. */
+ private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+ int height = Math.min(windowSize.y, mDismissAreaHeight);
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ height,
+ 0, windowSize.y - height,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ return lp;
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ public void showDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+
+ createOrUpdateDismissTarget();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+ // always invoke show, since the target might still be VISIBLE while playing hide animation,
+ // so we want to ensure it will show back again
+ mTargetViewContainer.show();
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ public void hideDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+ mTargetViewContainer.hide();
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ public void cleanUpDismissTarget() {
+ if (mTargetViewContainer.getParent() != null) {
+ mWindowManager.removeViewImmediate(mTargetViewContainer);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
new file mode 100644
index 000000000000..03547a55fa27
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.InputEvent;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the input consumer that allows the Shell to directly receive input.
+ */
+public class PipInputConsumer {
+
+ private static final String TAG = PipInputConsumer.class.getSimpleName();
+
+ /**
+ * Listener interface for callers to subscribe to input events.
+ */
+ public interface InputListener {
+ /** Handles any input event. */
+ boolean onInputEvent(InputEvent ev);
+ }
+
+ /**
+ * Listener interface for callers to learn when this class is registered or unregistered with
+ * window manager
+ */
+ private interface RegistrationListener {
+ void onRegistrationChanged(boolean isRegistered);
+ }
+
+ /**
+ * Input handler used for the input consumer. Input events are batched and consumed with the
+ * SurfaceFlinger vsync.
+ */
+ private final class InputEventReceiver extends BatchedInputEventReceiver {
+
+ InputEventReceiver(InputChannel inputChannel, Looper looper,
+ Choreographer choreographer) {
+ super(inputChannel, looper, choreographer);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = true;
+ try {
+ if (mListener != null) {
+ handled = mListener.onInputEvent(event);
+ }
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+ }
+
+ private final IWindowManager mWindowManager;
+ private final IBinder mToken;
+ private final String mName;
+ private final ShellExecutor mMainExecutor;
+
+ private InputEventReceiver mInputEventReceiver;
+ private InputListener mListener;
+ private RegistrationListener mRegistrationListener;
+
+ /**
+ * @param name the name corresponding to the input consumer that is defined in the system.
+ */
+ public PipInputConsumer(IWindowManager windowManager, String name,
+ ShellExecutor mainExecutor) {
+ mWindowManager = windowManager;
+ mToken = new Binder();
+ mName = name;
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * Sets the input listener.
+ */
+ public void setInputListener(InputListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Sets the registration listener.
+ */
+ public void setRegistrationListener(RegistrationListener listener) {
+ mRegistrationListener = listener;
+ mMainExecutor.execute(() -> {
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
+ }
+ });
+ }
+
+ /**
+ * Check if the InputConsumer is currently registered with WindowManager
+ *
+ * @return {@code true} if registered, {@code false} if not.
+ */
+ public boolean isRegistered() {
+ return mInputEventReceiver != null;
+ }
+
+ /**
+ * Registers the input consumer.
+ */
+ public void registerInputConsumer() {
+ if (mInputEventReceiver != null) {
+ return;
+ }
+ final InputChannel inputChannel = new InputChannel();
+ try {
+ // TODO(b/113087003): Support Picture-in-picture in multi-display.
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
+ mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to create input consumer, %s", TAG, e);
+ }
+ mMainExecutor.execute(() -> {
+ mInputEventReceiver = new InputEventReceiver(inputChannel,
+ Looper.myLooper(), Choreographer.getInstance());
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
+ }
+ });
+ }
+
+ /**
+ * Unregisters the input consumer.
+ */
+ public void unregisterInputConsumer() {
+ if (mInputEventReceiver == null) {
+ return;
+ }
+ try {
+ // TODO(b/113087003): Support Picture-in-picture in multi-display.
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to destroy input consumer, %s", TAG, e);
+ }
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ mMainExecutor.execute(() -> {
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
+ }
+ });
+ }
+
+ /**
+ * Dumps the {@link PipInputConsumer} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
new file mode 100644
index 000000000000..619bed4e19ca
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Debug;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.FloatProperties;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * A helper to animate and manipulate the PiP.
+ */
+public class PipMotionHelper implements PipAppOpsListener.Callback,
+ FloatingContentCoordinator.FloatingContent {
+ private static final String TAG = "PipMotionHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
+ private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+ private static final int UNSTASH_DURATION = 250;
+ private static final int LEAVE_PIP_DURATION = 300;
+ private static final int SHIFT_DURATION = 300;
+
+ /** Friction to use for PIP when it moves via physics fling animations. */
+ private static final float DEFAULT_FRICTION = 1.9f;
+ /** How much of the dismiss circle size to use when scaling down PIP. **/
+ private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
+
+ private final Context mContext;
+ private @NonNull PipBoundsState mPipBoundsState;
+
+ private PhonePipMenuController mMenuController;
+ private PipSnapAlgorithm mSnapAlgorithm;
+
+ /** The region that all of PIP must stay within. */
+ private final Rect mFloatingAllowedArea = new Rect();
+
+ /** Coordinator instance for resolving conflicts with other floating content. */
+ private FloatingContentCoordinator mFloatingContentCoordinator;
+
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ /**
+ * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
+ * using physics animations.
+ */
+ private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
+
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
+ */
+ private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
+
+ /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
+ private PhysicsAnimator.FlingConfig mFlingConfigX;
+ private PhysicsAnimator.FlingConfig mFlingConfigY;
+ /** FlingConfig instances provided to PhysicsAnimator for stashing. */
+ private PhysicsAnimator.FlingConfig mStashConfigX;
+
+ /** SpringConfig to use for fling-then-spring animations. */
+ private final PhysicsAnimator.SpringConfig mSpringConfig =
+ new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating into the dismiss region, matches the one in
+ * {@link MagnetizedObject}. */
+ private final PhysicsAnimator.SpringConfig mAnimateToDismissSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating the pip to catch up to the finger once it leaves the dismiss
+ * drag region. */
+ private final PhysicsAnimator.SpringConfig mCatchUpSpringConfig =
+ new PhysicsAnimator.SpringConfig(5000f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig to use for springing PIP away from conflicting floating content. */
+ private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+
+ private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
+ if (mPipBoundsState.getBounds().equals(newBounds)) {
+ return;
+ }
+
+ mMenuController.updateMenuLayout(newBounds);
+ mPipBoundsState.setBounds(newBounds);
+ };
+
+ /**
+ * Whether we're springing to the touch event location (vs. moving it to that position
+ * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+ * 'stuck' in the target and needs to catch up to the touch location.
+ */
+ private boolean mSpringingToTouch = false;
+
+ /**
+ * Whether PIP was released in the dismiss target, and will be animated out and dismissed
+ * shortly.
+ */
+ private boolean mDismissalPending = false;
+
+ /**
+ * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
+ * used to show menu activity when the expand animation is completed.
+ */
+ private Runnable mPostPipTransitionCallback;
+
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMenuController = menuController;
+ mSnapAlgorithm = snapAlgorithm;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mResizePipUpdateListener = (target, values) -> {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
+ */
+ }
+ };
+ }
+
+ void init() {
+ mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ }
+
+ @NonNull
+ @Override
+ public Rect getFloatingBoundsOnScreen() {
+ return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+ ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
+ }
+
+ @NonNull
+ @Override
+ public Rect getAllowedFloatingBoundsRegion() {
+ return mFloatingAllowedArea;
+ }
+
+ @Override
+ public void moveToBounds(@NonNull Rect bounds) {
+ animateToBounds(bounds, mConflictResolutionSpringConfig);
+ }
+
+ /**
+ * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
+ */
+ void synchronizePinnedStackBounds() {
+ cancelPhysicsAnimation();
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+ */
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ */
+ void movePip(Rect toBounds) {
+ movePip(toBounds, false /* isDragging */);
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ *
+ * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we
+ * won't notify the floating content coordinator of this move, since that will
+ * happen when the gesture ends.
+ */
+ void movePip(Rect toBounds, boolean isDragging) {
+ if (!isDragging) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ if (!mSpringingToTouch) {
+ // If we are moving PIP directly to the touch event locations, cancel any animations and
+ // move PIP to the given bounds.
+ cancelPhysicsAnimation();
+
+ if (!isDragging) {
+ resizePipUnchecked(toBounds);
+ mPipBoundsState.setBounds(toBounds);
+ } else {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
+ (Rect newBounds) -> {
+ mMenuController.updateMenuLayout(newBounds);
+ });
+ */
+ }
+ } else {
+ // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+ // to spring towards the new touch location.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_X, toBounds.left, mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_Y, toBounds.top, mCatchUpSpringConfig);
+
+ startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+ }
+ }
+
+ /** Animates the PIP into the dismiss target, scaling it down. */
+ void animateIntoDismissTarget(
+ MagnetizedObject.MagneticTarget target,
+ float velX, float velY,
+ boolean flung, Function0<Unit> after) {
+ final PointF targetCenter = target.getCenterOnScreen();
+
+ // PIP should fit in the circle
+ final float dismissCircleSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+
+ final float width = getBounds().width();
+ final float height = getBounds().height();
+ final float ratio = width / height;
+
+ // Width should be a little smaller than the circle size.
+ final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT;
+ final float desiredHeight = desiredWidth / ratio;
+ final float destinationX = targetCenter.x - (desiredWidth / 2f);
+ final float destinationY = targetCenter.y - (desiredHeight / 2f);
+
+ // If we're already in the dismiss target area, then there won't be a move to set the
+ // temporary bounds, so just initialize it to the current bounds.
+ if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig)
+ .withEndActions(after);
+
+ startBoundsAnimator(destinationX, destinationY);
+ }
+
+ /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+ void setSpringingToTouch(boolean springingToTouch) {
+ mSpringingToTouch = springingToTouch;
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * * fullscreen depending on the display area's windowing mode.
+ */
+ void expandLeavePip(boolean skipAnimation) {
+ expandLeavePip(skipAnimation, false /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned task to split-screen mode.
+ */
+ void expandIntoSplit() {
+ expandLeavePip(false, true /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * fullscreen depending on the display area's windowing mode.
+ */
+ private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exitPip: skipAnimation=%s"
+ + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+ }
+
+ /**
+ * Dismisses the pinned stack.
+ */
+ @Override
+ public void dismissPip() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
+ // mPipTaskOrganizer.removePip();
+ }
+
+ /** Sets the movement bounds to use to constrain PIP position animations. */
+ void onMovementBoundsChanged() {
+ rebuildFlingConfigs();
+
+ // The movement bounds represent the area within which we can move PIP's top-left position.
+ // The allowed area for all of PIP is those bounds plus PIP's width and height.
+ mFloatingAllowedArea.set(mPipBoundsState.getMovementBounds());
+ mFloatingAllowedArea.right += getBounds().width();
+ mFloatingAllowedArea.bottom += getBounds().height();
+ }
+
+ /**
+ * @return the PiP bounds.
+ */
+ private Rect getBounds() {
+ return mPipBoundsState.getBounds();
+ }
+
+ /**
+ * Flings the PiP to the closest snap target.
+ */
+ void flingToSnapTarget(
+ float velocityX, float velocityY, @Nullable Runnable postBoundsUpdateCallback) {
+ movetoTarget(velocityX, velocityY, postBoundsUpdateCallback, false /* isStash */);
+ }
+
+ /**
+ * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
+ */
+ void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+ velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+ movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
+ }
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ private void movetoTarget(
+ float velocityX,
+ float velocityY,
+ @Nullable Runnable postBoundsUpdateCallback,
+ boolean isStash) {
+ // If we're flinging to a snap target now, we're not springing to catch up to the touch
+ // location now.
+ mSpringingToTouch = false;
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
+ .flingThenSpring(
+ FloatProperties.RECT_X, velocityX,
+ isStash ? mStashConfigX : mFlingConfigX,
+ mSpringConfig, true /* flingMustReachMinOrMax */)
+ .flingThenSpring(
+ FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
+
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final float leftEdge = isStash
+ ? mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left
+ : mPipBoundsState.getMovementBounds().left;
+ final float rightEdge = isStash
+ ? mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right
+ : mPipBoundsState.getMovementBounds().right;
+
+ final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+ final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
+ final float estimatedFlingYEndValue =
+ PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
+
+ startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
+ postBoundsUpdateCallback);
+ }
+
+ /**
+ * Animates PIP to the provided bounds, using physics animations and the given spring
+ * configuration
+ */
+ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ // Animate from the current bounds if we're not already animating.
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, bounds.left, springConfig)
+ .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
+ startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
+ }
+
+ /**
+ * Animates the dismissal of the PiP off the edge of the screen.
+ */
+ void animateDismiss() {
+ // Animate off the bottom of the screen, then dismiss PIP.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_Y,
+ mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
+ 0,
+ mSpringConfig)
+ .withEndActions(this::dismissPip);
+
+ startBoundsAnimator(
+ getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
+
+ mDismissalPending = false;
+ }
+
+ /**
+ * Animates the PiP to the expanded state to show the menu.
+ */
+ float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
+ Rect expandedMovementBounds, Runnable callback) {
+ float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ movementBounds);
+ mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
+ mPostPipTransitionCallback = callback;
+ resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
+ return savedSnapFraction;
+ }
+
+ /**
+ * Animates the PiP from the expanded state to the normal state after the menu is hidden.
+ */
+ void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
+ Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) {
+ if (savedSnapFraction < 0f) {
+ // If there are no saved snap fractions, then just use the current bounds
+ savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ currentMovementBounds, mPipBoundsState.getStashedState());
+ }
+
+ mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction,
+ mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
+ mPipBoundsState.getDisplayBounds(),
+ mPipBoundsState.getDisplayLayout().stableInsets());
+
+ if (immediate) {
+ movePip(normalBounds);
+ } else {
+ resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
+ }
+ }
+
+ /**
+ * Animates the PiP to the stashed state, choosing the closest edge.
+ */
+ void animateToStashedClosestEdge() {
+ Rect tmpBounds = new Rect();
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final int stashType =
+ mPipBoundsState.getBounds().left == mPipBoundsState.getMovementBounds().left
+ ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT;
+ final float leftEdge = stashType == STASH_TYPE_LEFT
+ ? mPipBoundsState.getStashOffset()
+ - mPipBoundsState.getBounds().width() + insetBounds.left
+ : mPipBoundsState.getDisplayBounds().right
+ - mPipBoundsState.getStashOffset() - insetBounds.right;
+ tmpBounds.set((int) leftEdge,
+ mPipBoundsState.getBounds().top,
+ (int) (leftEdge + mPipBoundsState.getBounds().width()),
+ mPipBoundsState.getBounds().bottom);
+ resizeAndAnimatePipUnchecked(tmpBounds, UNSTASH_DURATION);
+ mPipBoundsState.setStashed(stashType);
+ }
+
+ /**
+ * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+ */
+ void animateToUnStashedBounds(Rect unstashedBounds) {
+ resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+ }
+
+ /**
+ * Animates the PiP to offset it from the IME or shelf.
+ */
+ void animateToOffset(Rect originalBounds, int offset) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: animateToOffset: originalBounds=%s offset=%s"
+ + " callers=\n%s", TAG, originalBounds, offset,
+ Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ /*
+ mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
+ mUpdateBoundsCallback);
+ */
+ }
+
+ /**
+ * Cancels all existing animations.
+ */
+ private void cancelPhysicsAnimation() {
+ mTemporaryBoundsPhysicsAnimator.cancel();
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ }
+
+ /** Set new fling configs whose min/max values respect the given movement bounds. */
+ private void rebuildFlingConfigs() {
+ mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().left,
+ mPipBoundsState.getMovementBounds().right);
+ mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().top,
+ mPipBoundsState.getMovementBounds().bottom);
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ mStashConfigX = new PhysicsAnimator.FlingConfig(
+ DEFAULT_FRICTION,
+ mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left,
+ mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right);
+ }
+
+ private void startBoundsAnimator(float toX, float toY) {
+ startBoundsAnimator(toX, toY, null /* postBoundsUpdateCallback */);
+ }
+
+ /**
+ * Starts the physics animator which will update the animated PIP bounds using physics
+ * animations, as well as the TimeAnimator which will apply those bounds to PIP.
+ *
+ * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
+ * the 'real' bounds to equal the final animated bounds.
+ *
+ * If one wishes to supply a callback after all the 'real' bounds update has happened,
+ * pass @param postBoundsUpdateCallback.
+ */
+ private void startBoundsAnimator(float toX, float toY, Runnable postBoundsUpdateCallback) {
+ if (!mSpringingToTouch) {
+ cancelPhysicsAnimation();
+ }
+
+ setAnimatingToBounds(new Rect(
+ (int) toX,
+ (int) toY,
+ (int) toX + getBounds().width(),
+ (int) toY + getBounds().height()));
+
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "startBoundsAnimator");
+ }
+ if (postBoundsUpdateCallback != null) {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd,
+ postBoundsUpdateCallback);
+ } else {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd);
+ }
+ }
+
+ mTemporaryBoundsPhysicsAnimator.start();
+ }
+
+ /**
+ * Notify that PIP was released in the dismiss target and will be animated out and dismissed
+ * shortly.
+ */
+ void notifyDismissalPending() {
+ mDismissalPending = true;
+ }
+
+ private void onBoundsPhysicsAnimationEnd() {
+ // The physics animation ended, though we may not necessarily be done animating, such as
+ // when we're still dragging after moving out of the magnetic target.
+ if (!mDismissalPending
+ && !mSpringingToTouch
+ && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // All motion operations have actually finished.
+ mPipBoundsState.setBounds(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+ if (!mDismissalPending) {
+ // do not schedule resize if PiP is dismissing, which may cause app re-open to
+ // mBounds instead of its normal bounds.
+ // mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
+ }
+ }
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ mDismissalPending = false;
+ cleanUpHighPerfSessionMaybe();
+ }
+
+ /**
+ * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
+ * we return these bounds from
+ * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
+ */
+ private void setAnimatingToBounds(Rect bounds) {
+ mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizePipUnchecked(Rect toBounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipUnchecked: toBounds=%s"
+ + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " "));
+ }
+ if (!toBounds.equals(getBounds())) {
+ // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
+ }
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
+ + " duration=%s callers=\n%s", TAG, toBounds, duration,
+ Debug.getCallers(5, " "));
+ }
+
+ // Intentionally resize here even if the current bounds match the destination bounds.
+ // This is so all the proper callbacks are performed.
+
+ // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
+ // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
+ // setAnimatingToBounds(toBounds);
+ }
+
+ /**
+ * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+ * magnetic dismiss target so it can calculate PIP's size and position.
+ */
+ MagnetizedObject<Rect> getMagnetizedPip() {
+ if (mMagnetizedPip == null) {
+ mMagnetizedPip = new MagnetizedObject<Rect>(
+ mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
+ FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ @Override
+ public float getWidth(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.width();
+ }
+
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
+
+ @Override
+ public void getLocationOnScreen(
+ @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+ loc[0] = animatedPipBounds.left;
+ loc[1] = animatedPipBounds.top;
+ }
+ };
+ mMagnetizedPip.setFlingToTargetEnabled(false);
+ }
+
+ return mMagnetizedPip;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
new file mode 100644
index 000000000000..04cf350ddd3e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.os.Looper;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
+ * trigger dynamic resize.
+ */
+public class PipResizeGestureHandler {
+
+ private static final String TAG = "PipResizeGestureHandler";
+ private static final int PINCH_RESIZE_SNAP_DURATION = 250;
+ private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+
+ private final Context mContext;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipBoundsState mPipBoundsState;
+ private final PipTouchState mPipTouchState;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
+ private final int mDisplayId;
+ private final ShellExecutor mMainExecutor;
+
+ private final PointF mDownPoint = new PointF();
+ private final PointF mDownSecondPoint = new PointF();
+ private final PointF mLastPoint = new PointF();
+ private final PointF mLastSecondPoint = new PointF();
+ private final Point mMaxSize = new Point();
+ private final Point mMinSize = new Point();
+ private final Rect mLastResizeBounds = new Rect();
+ private final Rect mUserResizeBounds = new Rect();
+ private final Rect mDownBounds = new Rect();
+ private final Runnable mUpdateMovementBoundsRunnable;
+ private final Consumer<Rect> mUpdateResizeBoundsCallback;
+
+ private float mTouchSlop;
+
+ private boolean mAllowGesture;
+ private boolean mIsAttached;
+ private boolean mIsEnabled;
+ private boolean mEnablePinchResize;
+ private boolean mIsSysUiStateValid;
+ private boolean mThresholdCrossed;
+ private boolean mOngoingPinchToResize = false;
+ private float mAngle = 0;
+ int mFirstIndex = -1;
+ int mSecondIndex = -1;
+
+ private InputMonitor mInputMonitor;
+ private InputEventReceiver mInputEventReceiver;
+
+ @Nullable
+ private final PipPerfHintController mPipPerfHintController;
+
+ @Nullable
+ private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private int mCtrlType;
+ private int mOhmOffset;
+
+ public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+ Runnable updateMovementBoundsRunnable,
+ PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
+ ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+ mContext = context;
+ mDisplayId = context.getDisplayId();
+ mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintController;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipBoundsState = pipBoundsState;
+ mPipTouchState = pipTouchState;
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ mPhonePipMenuController = menuActivityController;
+ mPipUiEventLogger = pipUiEventLogger;
+ mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mUpdateResizeBoundsCallback = (rect) -> {
+ mUserResizeBounds.set(rect);
+ // mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
+ }
+
+ void init() {
+ mContext.getDisplay().getRealSize(mMaxSize);
+ reloadResources();
+
+ final Resources res = mContext.getResources();
+ mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
+ }
+
+ void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mIsSysUiStateValid = isSysUiStateValid;
+ }
+
+ private void reloadResources() {
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ }
+
+ private void disposeInputChannel() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ }
+
+ void onActivityPinned() {
+ mIsAttached = true;
+ updateIsEnabled();
+ }
+
+ void onActivityUnpinned() {
+ mIsAttached = false;
+ mUserResizeBounds.setEmpty();
+ updateIsEnabled();
+ }
+
+ private void updateIsEnabled() {
+ boolean isEnabled = mIsAttached;
+ if (isEnabled == mIsEnabled) {
+ return;
+ }
+ mIsEnabled = isEnabled;
+ disposeInputChannel();
+
+ if (mIsEnabled) {
+ // Register input event receiver
+ mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
+ "pip-resize", mDisplayId);
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ mInputEventReceiver = new PipResizeInputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to create input event receiver", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void onInputEvent(InputEvent ev) {
+ if (!mEnablePinchResize) {
+ // No need to handle anything if neither form of resizing is enabled.
+ return;
+ }
+
+ if (!mPipTouchState.getAllowInputEvents()) {
+ // No need to handle anything if touches are not enabled
+ return;
+ }
+
+ // Don't allow resize when PiP is stashed.
+ if (mPipBoundsState.isStashed()) {
+ return;
+ }
+
+ if (ev instanceof MotionEvent) {
+ MotionEvent mv = (MotionEvent) ev;
+ int action = mv.getActionMasked();
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ if (!pipBounds.contains((int) mv.getRawX(), (int) mv.getRawY())
+ && mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+ }
+
+ if (mEnablePinchResize && mOngoingPinchToResize) {
+ onPinchResize(mv);
+ }
+ }
+ }
+
+ /**
+ * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize.
+ */
+ public boolean hasOngoingGesture() {
+ return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
+ }
+
+ public boolean isUsingPinchToZoom() {
+ return mEnablePinchResize;
+ }
+
+ public boolean isResizing() {
+ return mAllowGesture;
+ }
+
+ boolean willStartResizeGesture(MotionEvent ev) {
+ if (isInValidSysUiState()) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isInValidSysUiState() {
+ return mIsSysUiStateValid;
+ }
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ @VisibleForTesting
+ void onPinchResize(MotionEvent ev) {
+ int action = ev.getActionMasked();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mFirstIndex = -1;
+ mSecondIndex = -1;
+ mAllowGesture = false;
+ finishResize();
+ cleanUpHighPerfSessionMaybe();
+ }
+
+ if (ev.getPointerCount() != 2) {
+ return;
+ }
+
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mFirstIndex == -1 && mSecondIndex == -1
+ && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
+ && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
+ mAllowGesture = true;
+ mFirstIndex = 0;
+ mSecondIndex = 1;
+ mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
+ mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+ mDownBounds.set(pipBounds);
+
+ mLastPoint.set(mDownPoint);
+ mLastSecondPoint.set(mLastSecondPoint);
+ mLastResizeBounds.set(mDownBounds);
+
+ // start the high perf session as the second pointer gets detected
+ if (mPipPerfHintController != null) {
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "onPinchResize");
+ }
+ }
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mFirstIndex == -1 || mSecondIndex == -1) {
+ return;
+ }
+
+ float x0 = ev.getRawX(mFirstIndex);
+ float y0 = ev.getRawY(mFirstIndex);
+ float x1 = ev.getRawX(mSecondIndex);
+ float y1 = ev.getRawY(mSecondIndex);
+ mLastPoint.set(x0, y0);
+ mLastSecondPoint.set(x1, y1);
+
+ // Capture inputs
+ if (!mThresholdCrossed
+ && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop
+ || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) {
+ pilferPointers();
+ mThresholdCrossed = true;
+ // Reset the down to begin resizing from this point
+ mDownPoint.set(mLastPoint);
+ mDownSecondPoint.set(mLastSecondPoint);
+
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+ }
+
+ if (mThresholdCrossed) {
+ mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
+ mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
+ mDownBounds, mLastResizeBounds);
+
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
+ mAngle, null);
+ */
+ mPipBoundsState.setHasUserResizedPip(true);
+ }
+ }
+ }
+
+ private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
+ final int leftEdge = bounds.left;
+
+
+ final int fromLeft = Math.abs(leftEdge - movementBounds.left);
+ final int fromRight = Math.abs(movementBounds.right - leftEdge);
+
+ // The PIP will be snapped to either the right or left edge, so calculate which one
+ // is closest to the current position.
+ final int newLeft = fromLeft < fromRight
+ ? movementBounds.left : movementBounds.right;
+
+ bounds.offsetTo(newLeft, mLastResizeBounds.top);
+ }
+
+ /**
+ * Resizes the pip window and updates user-resized bounds.
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ Rect finalBounds = new Rect(bounds);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds);
+
+ // snap the target bounds to the either left or right edge, by choosing the closer one
+ snapToMovementBoundsEdge(finalBounds, movementBounds);
+
+ // apply the requested snap fraction onto the target bounds
+ mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction);
+
+ // resize from current bounds to target bounds without animation
+ // mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null);
+ // set the flag that pip has been resized
+ mPipBoundsState.setHasUserResizedPip(true);
+
+ // finish the resize operation and update the state of the bounds
+ // mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
+ }
+
+ private void finishResize() {
+ if (!mLastResizeBounds.isEmpty()) {
+ // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
+ // position correctly. Drag-resize does not need to move, so just finalize resize.
+ if (mOngoingPinchToResize) {
+ final Rect startBounds = new Rect(mLastResizeBounds);
+ // If user resize is pretty close to max size, just auto resize to max.
+ if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+ || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+ }
+
+ // If user resize is smaller than min size, auto resize to min
+ if (mLastResizeBounds.width() < mMinSize.x
+ || mLastResizeBounds.height() < mMinSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ }
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm
+ .getMovementBounds(mLastResizeBounds);
+
+ // snap mLastResizeBounds to the correct edge based on movement bounds
+ snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mLastResizeBounds, movementBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // disable any touch events beyond resizing too
+ mPipTouchState.setAllowInputEvents(false);
+
+ /*
+ mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+ // enable touch events
+ mPipTouchState.setAllowInputEvents(true);
+ });
+ */
+ } else {
+ /*
+ mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
+ TRANSITION_DIRECTION_USER_RESIZE,
+ mUpdateResizeBoundsCallback);
+ */
+ }
+ final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
+ } else {
+ resetState();
+ }
+ }
+
+ private void resetState() {
+ mCtrlType = CTRL_NONE;
+ mAngle = 0;
+ mOngoingPinchToResize = false;
+ mAllowGesture = false;
+ mThresholdCrossed = false;
+ }
+
+ void setUserResizeBounds(Rect bounds) {
+ mUserResizeBounds.set(bounds);
+ }
+
+ void invalidateUserResizeBounds() {
+ mUserResizeBounds.setEmpty();
+ }
+
+ Rect getUserResizeBounds() {
+ return mUserResizeBounds;
+ }
+
+ @VisibleForTesting
+ Rect getLastResizeBounds() {
+ return mLastResizeBounds;
+ }
+
+ @VisibleForTesting
+ void pilferPointers() {
+ mInputMonitor.pilferPointers();
+ }
+
+
+ void updateMaxSize(int maxX, int maxY) {
+ mMaxSize.set(maxX, maxY);
+ }
+
+ void updateMinSize(int minX, int minY) {
+ mMinSize.set(minX, minY);
+ }
+
+ void setOhmOffset(int offset) {
+ mOhmOffset = offset;
+ }
+
+ private float distanceBetween(PointF p1, PointF p2) {
+ return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
+ }
+
+ private void resizeRectAboutCenter(Rect rect, int w, int h) {
+ int cx = rect.centerX();
+ int cy = rect.centerY();
+ int l = cx - w / 2;
+ int r = l + w;
+ int t = cy - h / 2;
+ int b = t + h;
+ rect.set(l, t, r, b);
+ }
+
+ /**
+ * Dumps the {@link PipResizeGestureHandler} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
+ pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
+ pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
+ pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
+ pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
+ pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+ }
+
+ class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
+ PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper, Choreographer.getInstance());
+ }
+
+ public void onInputEvent(InputEvent event) {
+ PipResizeGestureHandler.this.onInputEvent(event);
+ finishInputEvent(event, true);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java
new file mode 100644
index 000000000000..efa5fc8bf8b1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+/**
+ * A generic interface for a touch gesture.
+ */
+public abstract class PipTouchGesture {
+
+ /**
+ * Handle the touch down.
+ */
+ public void onDown(PipTouchState touchState) {}
+
+ /**
+ * Handle the touch move, and return whether the event was consumed.
+ */
+ public boolean onMove(PipTouchState touchState) {
+ return false;
+ }
+
+ /**
+ * Handle the touch up, and return whether the gesture was consumed.
+ */
+ public boolean onUp(PipTouchState touchState) {
+ return false;
+ }
+
+ /**
+ * Cleans up the high performance hint session if needed.
+ */
+ public void cleanUpHighPerfSessionMaybe() {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
new file mode 100644
index 000000000000..cc8e3e0934e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.provider.DeviceConfig;
+import android.util.Size;
+import android.view.DisplayCutout;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+
+ private static final String TAG = "PipTouchHandler";
+ private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
+
+ // Allow PIP to resize to a slightly bigger state upon touch
+ private boolean mEnableResize;
+ private final Context mContext;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final SizeSpecSource mSizeSpecSource;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final PipDismissTargetHandler mPipDismissTargetHandler;
+ private final ShellExecutor mMainExecutor;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+
+ private PipResizeGestureHandler mPipResizeGestureHandler;
+
+ private final PhonePipMenuController mMenuController;
+ private final AccessibilityManager mAccessibilityManager;
+
+ /**
+ * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
+ * screen, it will be shown in "stashed" mode, where PIP will only show partially.
+ */
+ private boolean mEnableStash = true;
+
+ private float mStashVelocityThreshold;
+
+ // The reference inset bounds, used to determine the dismiss fraction
+ private final Rect mInsetBounds = new Rect();
+
+ // Used to workaround an issue where the WM rotation happens before we are notified, allowing
+ // us to send stale bounds
+ private int mDeferResizeToNormalBoundsUntilRotation = -1;
+ private int mDisplayRotation;
+
+ // Behaviour states
+ private int mMenuState = MENU_STATE_NONE;
+ private boolean mIsImeShowing;
+ private int mImeHeight;
+ private int mImeOffset;
+ private boolean mIsShelfShowing;
+ private int mShelfHeight;
+ private int mMovementBoundsExtraOffsets;
+ private int mBottomOffsetBufferPx;
+ private float mSavedSnapFraction = -1f;
+ private boolean mSendingHoverAccessibilityEvents;
+ private boolean mMovementWithinDismiss;
+
+ // Touch state
+ private final PipTouchState mTouchState;
+ private final FloatingContentCoordinator mFloatingContentCoordinator;
+ private PipMotionHelper mMotionHelper;
+ private PipTouchGesture mGesture;
+
+ // Temp vars
+ private final Rect mTmpBounds = new Rect();
+
+ /**
+ * A listener for the PIP menu activity.
+ */
+ private class PipMenuListener implements PhonePipMenuController.Listener {
+ @Override
+ public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback);
+ }
+
+ @Override
+ public void onPipMenuStateChangeFinish(int menuState) {
+ setMenuState(menuState);
+ }
+
+ @Override
+ public void onPipExpand() {
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+
+ @Override
+ public void onPipDismiss() {
+ mTouchState.removeDoubleTapTimeoutCallback();
+ mMotionHelper.dismissPip();
+ }
+
+ @Override
+ public void onPipShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ public PipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipBoundsState = pipBoundsState;
+ mSizeSpecSource = sizeSpecSource;
+ mMenuController = menuController;
+ mPipUiEventLogger = pipUiEventLogger;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mMenuController.addListener(new PipMenuListener());
+ mGesture = new DefaultPipTouchGesture();
+ mMotionHelper = pipMotionHelper;
+ mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
+ mMotionHelper, mainExecutor);
+ mTouchState = new PipTouchState(ViewConfiguration.get(context),
+ () -> {
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ },
+ menuController::hideMenu,
+ mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mTouchState, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor, mPipPerfHintController);
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ /**
+ * Called when the touch handler is initialized.
+ */
+ public void onInit() {
+ Resources res = mContext.getResources();
+ mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
+ reloadResources();
+
+ mMotionHelper.init();
+ mPipResizeGestureHandler.init();
+ mPipDismissTargetHandler.init();
+
+ mEnableStash = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASHING,
+ /* defaultValue = */ true);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASHING)) {
+ mEnableStash = properties.getBoolean(
+ PIP_STASHING, /* defaultValue = */ true);
+ }
+ });
+ mStashVelocityThreshold = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+ mStashVelocityThreshold = properties.getFloat(
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ }
+ });
+ }
+
+ public PipTransitionController getTransitionHandler() {
+ // return mPipTaskOrganizer.getTransitionController();
+ return null;
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+ mPipDismissTargetHandler.updateMagneticTargetSize();
+ }
+
+ void onOverlayChanged() {
+ // onOverlayChanged is triggered upon theme change, update the dismiss target accordingly.
+ mPipDismissTargetHandler.init();
+ }
+
+ private boolean shouldShowResizeHandle() {
+ return false;
+ }
+
+ void setTouchGesture(PipTouchGesture gesture) {
+ mGesture = gesture;
+ }
+
+ void setTouchEnabled(boolean enabled) {
+ mTouchState.setAllowTouches(enabled);
+ }
+
+ void showPictureInPictureMenu() {
+ // Only show the menu if the user isn't currently interacting with the PiP
+ if (!mTouchState.isUserInteracting()) {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ }
+
+ void onActivityPinned() {
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+
+ mPipResizeGestureHandler.onActivityPinned();
+ mFloatingContentCoordinator.onContentAdded(mMotionHelper);
+ }
+
+ void onActivityUnpinned(ComponentName topPipActivity) {
+ if (topPipActivity == null) {
+ // Clean up state after the last PiP activity is removed
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+
+ mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
+ }
+ mPipResizeGestureHandler.onActivityUnpinned();
+ }
+
+ void onPinnedStackAnimationEnded(
+ @PipAnimationController.TransitionDirection int direction) {
+ // Always synchronize the motion helper bounds once PiP animations finish
+ mMotionHelper.synchronizePinnedStackBounds();
+ updateMovementBounds();
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // Set the initial bounds as the user resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ }
+ }
+
+ void onConfigurationChanged() {
+ mPipResizeGestureHandler.onConfigurationChanged();
+ mMotionHelper.synchronizePinnedStackBounds();
+ reloadResources();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ // Recreate the dismiss target for the new orientation.
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+ }
+ */
+ }
+
+ void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mIsImeShowing = imeVisible;
+ mImeHeight = imeHeight;
+ }
+
+ void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ mIsShelfShowing = shelfVisible;
+ mShelfHeight = shelfHeight;
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
+ }
+
+ void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+ final Rect toMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+ final int prevBottom = mPipBoundsState.getMovementBounds().bottom
+ - mMovementBoundsExtraOffsets;
+ if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+ outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+ }
+ }
+
+ /**
+ * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+ */
+ public void onAspectRatioChanged() {
+ mPipResizeGestureHandler.invalidateUserResizeBounds();
+ }
+
+ void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+ // Set the user resized bounds equal to the new normal bounds in case they were
+ // invalidated (e.g. by an aspect ratio change).
+ if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+ mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+ }
+
+ final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+ final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
+ if (fromDisplayRotationChanged) {
+ mTouchState.reset();
+ }
+
+ // Re-calculate the expanded bounds
+ Rect normalMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
+ normalMovementBounds, bottomOffset);
+
+ if (mPipBoundsState.getMovementBounds().isEmpty()) {
+ // mMovementBounds is not initialized yet and a clean movement bounds without
+ // bottom offset shall be used later in this function.
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
+ mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
+ }
+
+ // Calculate the expanded size
+ float aspectRatio = (float) normalBounds.width() / normalBounds.height();
+ Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
+ mPipBoundsState.setExpandedBounds(
+ new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
+ Rect expandedMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(
+ mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
+ bottomOffset);
+
+ updatePipSizeConstraints(normalBounds, aspectRatio);
+
+ // The extra offset does not really affect the movement bounds, but are applied based on the
+ // current state (ime showing, or shelf offset) when we need to actually shift
+ int extraOffset = Math.max(
+ mIsImeShowing ? mImeOffset : 0,
+ !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
+
+ // Update the movement bounds after doing the calculations based on the old movement bounds
+ // above
+ mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
+ mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
+ mDisplayRotation = displayRotation;
+ mInsetBounds.set(insetBounds);
+ updateMovementBounds();
+ mMovementBoundsExtraOffsets = extraOffset;
+
+ // If we have a deferred resize, apply it now
+ if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
+ mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
+ mPipBoundsState.getNormalMovementBounds(), mPipBoundsState.getMovementBounds(),
+ true /* immediate */);
+ mSavedSnapFraction = -1f;
+ mDeferResizeToNormalBoundsUntilRotation = -1;
+ }
+ }
+
+ /**
+ * Update the values for min/max allowed size of picture in picture window based on the aspect
+ * ratio.
+ * @param aspectRatio aspect ratio to use for the calculation of min/max size
+ */
+ public void updateMinMaxSize(float aspectRatio) {
+ updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
+ aspectRatio);
+ }
+
+ private void updatePipSizeConstraints(Rect normalBounds,
+ float aspectRatio) {
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ updatePinchResizeSizeConstraints(aspectRatio);
+ } else {
+ mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+ mPipBoundsState.getExpandedBounds().height());
+ }
+ }
+
+ private void updatePinchResizeSizeConstraints(float aspectRatio) {
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public void onRegistrationChanged(boolean isRegistered) {
+ if (isRegistered) {
+ // Register the accessibility connection.
+ } else {
+ mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+ }
+ if (!isRegistered && mTouchState.isUserInteracting()) {
+ // If the input consumer is unregistered while the user is interacting, then we may not
+ // get the final TOUCH_UP event, so clean up the dismiss target as well
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+ }
+ }
+
+ private void onAccessibilityShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public boolean handleTouchEvent(InputEvent inputEvent) {
+ // Skip any non motion events
+ if (!(inputEvent instanceof MotionEvent)) {
+ return true;
+ }
+
+ // do not process input event if not allowed
+ if (!mTouchState.getAllowInputEvents()) {
+ return true;
+ }
+
+ MotionEvent ev = (MotionEvent) inputEvent;
+ if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+ // Initialize the touch state for the gesture, but immediately reset to invalidate the
+ // gesture
+ mTouchState.onTouchEvent(ev);
+ mTouchState.reset();
+ return true;
+ }
+
+ if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mGesture.cleanUpHighPerfSessionMaybe();
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ return true;
+ }
+
+ if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
+ && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
+ // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+ // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+ // events it'll receive if the object is dragged out of the magnetic field.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchState.onTouchEvent(ev);
+ }
+
+ // Continue tracking velocity when the object is in the magnetic field, since we want to
+ // respect touch input velocity if the object is dragged out and then flung.
+ mTouchState.addMovementToVelocityTracker(ev);
+
+ return true;
+ }
+
+ if (!mTouchState.isUserInteracting()) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Waiting to start the entry animation, skip the motion event.", TAG);
+ return true;
+ }
+
+ // Update the touch state
+ mTouchState.onTouchEvent(ev);
+
+ boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mGesture.onDown(mTouchState);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mGesture.onMove(mTouchState)) {
+ break;
+ }
+
+ shouldDeliverToMenu = !mTouchState.isDragging();
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Update the movement bounds again if the state has changed since the user started
+ // dragging (ie. when the IME shows)
+ updateMovementBounds();
+
+ if (mGesture.onUp(mTouchState)) {
+ break;
+ }
+ }
+ // Fall through to clean up
+ case MotionEvent.ACTION_CANCEL: {
+ shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
+ mTouchState.reset();
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_ENTER: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.removeHoverExitTimeoutCallback();
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, false /* willResizeMenu */,
+ shouldShowResizeHandle());
+ }
+ }
+ // Fall through
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ }
+ if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mSendingHoverAccessibilityEvents = false;
+ }
+ break;
+ }
+ }
+
+ shouldDeliverToMenu &= !mPipBoundsState.isStashed();
+
+ // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+ if (shouldDeliverToMenu) {
+ final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+ // Send the cancel event and cancel menu timeout if it starts to drag.
+ if (mTouchState.startedDragging()) {
+ cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+ mMenuController.pokeMenu();
+ }
+
+ mMenuController.handlePointerEvent(cloneEvent);
+ cloneEvent.recycle();
+ }
+
+ return true;
+ }
+
+ private void sendAccessibilityHoverEvent(int type) {
+ if (!mAccessibilityManager.isEnabled()) {
+ return;
+ }
+
+ AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ event.setImportantForAccessibility(true);
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+ event.setWindowId(
+ AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Called when the PiP menu state is in the process of animating/changing from one to another.
+ */
+ private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ if (mMenuState == menuState && !resize) {
+ return;
+ }
+
+ if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
+ // Save the current snap fraction and if we do not drag or move the PiP, then
+ // we store back to this snap fraction. Otherwise, we'll reset the snap
+ // fraction and snap to the closest edge.
+ if (resize) {
+ // PIP is too small to show the menu actions and thus needs to be resized to a
+ // size that can fit them all. Resize to the default size.
+ animateToNormalSize(callback);
+ }
+ } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
+ // Try and restore the PiP to the closest edge, using the saved snap fraction
+ // if possible
+ if (resize && !mPipResizeGestureHandler.isResizing()) {
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ // This is a very special case: when the menu is expanded and visible,
+ // navigating to another activity can trigger auto-enter PiP, and if the
+ // revealed activity has a forced rotation set, then the controller will get
+ // updated with the new rotation of the display. However, at the same time,
+ // SystemUI will try to hide the menu by creating an animation to the normal
+ // bounds which are now stale. In such a case we defer the animation to the
+ // normal bounds until after the next onMovementBoundsChanged() call to get the
+ // bounds in the new orientation
+ int displayRotation = mContext.getDisplay().getRotation();
+ if (mDisplayRotation != displayRotation) {
+ mDeferResizeToNormalBoundsUntilRotation = displayRotation;
+ }
+ }
+
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ mSavedSnapFraction = -1f;
+ }
+ }
+ }
+
+ private void setMenuState(int menuState) {
+ mMenuState = menuState;
+ updateMovementBounds();
+ // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+ // as well, or it can't handle a11y focus and pip menu can't perform any action.
+ onRegistrationChanged(menuState == MENU_STATE_NONE);
+ if (menuState == MENU_STATE_NONE) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+ } else if (menuState == MENU_STATE_FULL) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
+ }
+ }
+
+ private void animateToMaximizedState(Runnable callback) {
+ Rect maxMovementBounds = new Rect();
+ Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
+ mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
+ mPipBoundsState.getMovementBounds(), maxMovementBounds,
+ callback);
+ }
+
+ private void animateToNormalSize(Runnable callback) {
+ // Save the current bounds as the user-resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+
+ final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
+ final Rect normalBounds = mPipBoundsState.getNormalBounds();
+ final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
+ minMenuSize);
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(destBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
+ mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ }
+
+ private void animateToUnexpandedState(Rect restoreBounds) {
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+ restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ mSavedSnapFraction = -1f;
+ }
+
+ private void animateToUnStashedState() {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+ final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+ unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+ : mInsetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+ : mInsetBounds.right;
+ mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+ }
+
+ /**
+ * @return the motion helper.
+ */
+ public PipMotionHelper getMotionHelper() {
+ return mMotionHelper;
+ }
+
+ @VisibleForTesting
+ public PipResizeGestureHandler getPipResizeGestureHandler() {
+ return mPipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
+ mPipResizeGestureHandler = pipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
+ mMotionHelper = pipMotionHelper;
+ }
+
+ Rect getUserResizeBounds() {
+ return mPipResizeGestureHandler.getUserResizeBounds();
+ }
+
+ /**
+ * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
+ */
+ void setUserResizeBounds(Rect bounds) {
+ mPipResizeGestureHandler.setUserResizeBounds(bounds);
+ }
+
+ /**
+ * Gesture controlling normal movement of the PIP.
+ */
+ private class DefaultPipTouchGesture extends PipTouchGesture {
+ private final Point mStartPosition = new Point();
+ private final PointF mDelta = new PointF();
+ private boolean mShouldHideMenuAfterFling;
+
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ @Override
+ public void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ @Override
+ public void onDown(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return;
+ }
+
+ if (mPipPerfHintController != null) {
+ // Cache the PiP high perf session to close it upon touch up.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+ }
+
+ Rect bounds = getPossiblyMotionBounds();
+ mDelta.set(0f, 0f);
+ mStartPosition.set(bounds.left, bounds.top);
+ mMovementWithinDismiss = touchState.getDownTouchPosition().y
+ >= mPipBoundsState.getMovementBounds().bottom;
+ mMotionHelper.setSpringingToTouch(false);
+ // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl());
+
+ // If the menu is still visible then just poke the menu
+ // so that it will timeout after the user stops touching it
+ if (mMenuState != MENU_STATE_NONE && !mPipBoundsState.isStashed()) {
+ mMenuController.pokeMenu();
+ }
+ }
+
+ @Override
+ public boolean onMove(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ if (touchState.startedDragging()) {
+ mSavedSnapFraction = -1f;
+ mPipDismissTargetHandler.showDismissTargetMaybe();
+ }
+
+ if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
+ // Move the pinned stack freely
+ final PointF lastDelta = touchState.getLastTouchDelta();
+ float lastX = mStartPosition.x + mDelta.x;
+ float lastY = mStartPosition.y + mDelta.y;
+ float left = lastX + lastDelta.x;
+ float top = lastY + lastDelta.y;
+
+ // Add to the cumulative delta after bounding the position
+ mDelta.x += left - lastX;
+ mDelta.y += top - lastY;
+
+ mTmpBounds.set(getPossiblyMotionBounds());
+ mTmpBounds.offsetTo((int) left, (int) top);
+ mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
+
+ final PointF curPos = touchState.getLastTouchPosition();
+ if (mMovementWithinDismiss) {
+ // Track if movement remains near the bottom edge to identify swipe to dismiss
+ mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onUp(PipTouchState touchState) {
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ mPipDismissTargetHandler.setTaskLeash(null);
+
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ final PointF vel = touchState.getVelocity();
+
+ if (touchState.isDragging()) {
+ if (mMenuState != MENU_STATE_NONE) {
+ // If the menu is still visible, then just poke the menu so that
+ // it will timeout after the user stops touching it
+ mMenuController.showMenu(mMenuState, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
+
+ // Reset the touch state on up before the fling settles
+ mTouchState.reset();
+ if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+ mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
+ } else {
+ if (mPipBoundsState.isStashed()) {
+ // Reset stashed state if previously stashed
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ }
+ mMotionHelper.flingToSnapTarget(vel.x, vel.y,
+ this::flingEndAction /* endAction */);
+ }
+ } else if (mTouchState.isDoubleTap() && !mPipBoundsState.isStashed()
+ && mMenuState != MENU_STATE_FULL) {
+ // If using pinch to zoom, double-tap functions as resizing between max/min size
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ final boolean toExpand = mPipBoundsState.getBounds().width()
+ < mPipBoundsState.getMaxSize().x
+ && mPipBoundsState.getBounds().height()
+ < mPipBoundsState.getMaxSize().y;
+ if (mMenuController.isMenuVisible()) {
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ }
+
+ // the size to toggle to after a double tap
+ int nextSize = PipDoubleTapHelper
+ .nextSizeSpec(mPipBoundsState, getUserResizeBounds());
+
+ // actually toggle to the size chosen
+ if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToMaximizedState(null);
+ } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToNormalSize(null);
+ } else {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ // Expand to fullscreen if this is a double tap
+ // the PiP should be frozen until the transition ends
+ setTouchEnabled(false);
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+ } else if (mMenuState != MENU_STATE_FULL) {
+ if (mPipBoundsState.isStashed()) {
+ // Unstash immediately if stashed, and don't wait for the double tap timeout
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ mTouchState.removeDoubleTapTimeoutCallback();
+ } else if (!mTouchState.isWaitingForDoubleTap()) {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ } else {
+ // Next touch event _may_ be the second tap for the double-tap, schedule a
+ // fallback runnable to trigger the menu if no touch event occurs before the
+ // next tap
+ mTouchState.scheduleDoubleTapTimeoutCallback();
+ }
+ }
+ cleanUpHighPerfSessionMaybe();
+ return true;
+ }
+
+ private void stashEndAction() {
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
+ }
+
+ private void flingEndAction() {
+ if (mShouldHideMenuAfterFling) {
+ // If the menu is not visible, then we can still be showing the activity for the
+ // dismiss overlay, so just finish it after the animation completes
+ mMenuController.hideMenu();
+ }
+ }
+
+ private boolean shouldStash(PointF vel, Rect motionBounds) {
+ final boolean flingToLeft = vel.x < -mStashVelocityThreshold;
+ final boolean flingToRight = vel.x > mStashVelocityThreshold;
+ final int offset = motionBounds.width() / 2;
+ final boolean droppingOnLeft =
+ motionBounds.left < mPipBoundsState.getDisplayBounds().left - offset;
+ final boolean droppingOnRight =
+ motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset;
+
+ // Do not allow stash if the destination edge contains display cutout. We only
+ // compare the left and right edges since we do not allow stash on top / bottom.
+ final DisplayCutout displayCutout =
+ mPipBoundsState.getDisplayLayout().getDisplayCutout();
+ if (displayCutout != null) {
+ if ((flingToLeft || droppingOnLeft)
+ && !displayCutout.getBoundingRectLeft().isEmpty()) {
+ return false;
+ } else if ((flingToRight || droppingOnRight)
+ && !displayCutout.getBoundingRectRight().isEmpty()) {
+ return false;
+ }
+ }
+
+ // If user flings the PIP window above the minimum velocity, stash PIP.
+ // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+ // edge.
+ final boolean stashFromFlingToEdge =
+ (flingToLeft && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
+ || (flingToRight && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT);
+
+ // If User releases the PIP window while it's out of the display bounds, put
+ // PIP into stashed mode.
+ final boolean stashFromDroppingOnEdge = droppingOnLeft || droppingOnRight;
+
+ return stashFromFlingToEdge || stashFromDroppingOnEdge;
+ }
+ }
+
+ /**
+ * Updates the current movement bounds based on whether the menu is currently visible and
+ * resized.
+ */
+ private void updateMovementBounds() {
+ mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
+ mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.onMovementBoundsChanged();
+ }
+
+ private Rect getMovementBounds(Rect curBounds) {
+ Rect movementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ movementBounds, mIsImeShowing ? mImeHeight : 0);
+ return movementBounds;
+ }
+
+ /**
+ * @return {@code true} if the menu should be resized on tap because app explicitly specifies
+ * PiP window size that is too small to hold all the actions.
+ */
+ private boolean willResizeMenu() {
+ if (!mEnableResize) {
+ return false;
+ }
+ final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+ if (estimatedMinMenuSize == null) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get estimated menu size", TAG);
+ return false;
+ }
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ return currentBounds.width() < estimatedMinMenuSize.getWidth()
+ || currentBounds.height() < estimatedMinMenuSize.getHeight();
+ }
+
+ /**
+ * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+ * temporary motion bounds otherwise.
+ */
+ Rect getPossiblyMotionBounds() {
+ return mPipBoundsState.getMotionBoundsState().isInMotion()
+ ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
+ : mPipBoundsState.getBounds();
+ }
+
+ void setOhmOffset(int offset) {
+ mPipResizeGestureHandler.setOhmOffset(offset);
+ }
+
+ /**
+ * Dumps the {@link PipTouchHandler} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mMenuState=" + mMenuState);
+ pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+ pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+ pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+ pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
+ pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
+ mPipBoundsAlgorithm.dump(pw, innerPrefix);
+ mTouchState.dump(pw, innerPrefix);
+ if (mPipResizeGestureHandler != null) {
+ mPipResizeGestureHandler.dump(pw, innerPrefix);
+ }
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
new file mode 100644
index 000000000000..d093f1e5ccc1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import android.graphics.PointF;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * This keeps track of the touch state throughout the current touch gesture.
+ */
+public class PipTouchState {
+ private static final String TAG = "PipTouchState";
+ private static final boolean DEBUG = false;
+
+ @VisibleForTesting
+ public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+ static final long HOVER_EXIT_TIMEOUT = 50;
+
+ private final ShellExecutor mMainExecutor;
+ private final ViewConfiguration mViewConfig;
+ private final Runnable mDoubleTapTimeoutCallback;
+ private final Runnable mHoverExitTimeoutCallback;
+
+ private VelocityTracker mVelocityTracker;
+ private long mDownTouchTime = 0;
+ private long mLastDownTouchTime = 0;
+ private long mUpTouchTime = 0;
+ private final PointF mDownTouch = new PointF();
+ private final PointF mDownDelta = new PointF();
+ private final PointF mLastTouch = new PointF();
+ private final PointF mLastDelta = new PointF();
+ private final PointF mVelocity = new PointF();
+ private boolean mAllowTouches = true;
+
+ // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing
+ private boolean mAllowInputEvents = true;
+ private boolean mIsUserInteracting = false;
+ // Set to true only if the multiple taps occur within the double tap timeout
+ private boolean mIsDoubleTap = false;
+ // Set to true only if a gesture
+ private boolean mIsWaitingForDoubleTap = false;
+ private boolean mIsDragging = false;
+ // The previous gesture was a drag
+ private boolean mPreviouslyDragging = false;
+ private boolean mStartedDragging = false;
+ private boolean mAllowDraggingOffscreen = false;
+ private int mActivePointerId;
+ private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
+
+ public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
+ Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
+ mViewConfig = viewConfig;
+ mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
+ mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * @return true if input processing is enabled for PiP in general.
+ */
+ public boolean getAllowInputEvents() {
+ return mAllowInputEvents;
+ }
+
+ /**
+ * @param allowInputEvents true to enable input processing for PiP in general.
+ */
+ public void setAllowInputEvents(boolean allowInputEvents) {
+ mAllowInputEvents = allowInputEvents;
+ }
+
+ /**
+ * Resets this state.
+ */
+ public void reset() {
+ mAllowDraggingOffscreen = false;
+ mIsDragging = false;
+ mStartedDragging = false;
+ mIsUserInteracting = false;
+ mLastTouchDisplayId = Display.INVALID_DISPLAY;
+ }
+
+ /**
+ * Processes a given touch event and updates the state.
+ */
+ public void onTouchEvent(MotionEvent ev) {
+ mLastTouchDisplayId = ev.getDisplayId();
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (!mAllowTouches) {
+ return;
+ }
+
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ addMovementToVelocityTracker(ev);
+
+ mActivePointerId = ev.getPointerId(0);
+ if (DEBUG) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Setting active pointer id on DOWN: %d", TAG, mActivePointerId);
+ }
+ mLastTouch.set(ev.getRawX(), ev.getRawY());
+ mDownTouch.set(mLastTouch);
+ mAllowDraggingOffscreen = true;
+ mIsUserInteracting = true;
+ mDownTouchTime = ev.getEventTime();
+ mIsDoubleTap = !mPreviouslyDragging
+ && (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+ mIsWaitingForDoubleTap = false;
+ mIsDragging = false;
+ mLastDownTouchTime = mDownTouchTime;
+ if (mDoubleTapTimeoutCallback != null) {
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on MOVE: %d", TAG, mActivePointerId);
+ break;
+ }
+
+ float x = ev.getRawX(pointerIndex);
+ float y = ev.getRawY(pointerIndex);
+ mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
+ mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
+
+ boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
+ if (!mIsDragging) {
+ if (hasMovedBeyondTap) {
+ mIsDragging = true;
+ mStartedDragging = true;
+ }
+ } else {
+ mStartedDragging = false;
+ }
+ mLastTouch.set(x, y);
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // Select a new active pointer id and reset the movement state
+ final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (DEBUG) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Relinquish active pointer id on POINTER_UP: %d",
+ TAG, mActivePointerId);
+ }
+ mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+ mVelocityTracker.computeCurrentVelocity(1000,
+ mViewConfig.getScaledMaximumFlingVelocity());
+ mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on UP: %d", TAG, mActivePointerId);
+ break;
+ }
+
+ mUpTouchTime = ev.getEventTime();
+ mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
+ mPreviouslyDragging = mIsDragging;
+ mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging
+ && (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+
+ }
+ // fall through to clean up
+ case MotionEvent.ACTION_CANCEL: {
+ recycleVelocityTracker();
+ break;
+ }
+ case MotionEvent.ACTION_BUTTON_PRESS: {
+ removeHoverExitTimeoutCallback();
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return the velocity of the active touch pointer at the point it is lifted off the screen.
+ */
+ public PointF getVelocity() {
+ return mVelocity;
+ }
+
+ /**
+ * @return the last touch position of the active pointer.
+ */
+ public PointF getLastTouchPosition() {
+ return mLastTouch;
+ }
+
+ /**
+ * @return the movement delta between the last handled touch event and the previous touch
+ * position.
+ */
+ public PointF getLastTouchDelta() {
+ return mLastDelta;
+ }
+
+ /**
+ * @return the down touch position.
+ */
+ public PointF getDownTouchPosition() {
+ return mDownTouch;
+ }
+
+ /**
+ * @return the movement delta between the last handled touch event and the down touch
+ * position.
+ */
+ public PointF getDownTouchDelta() {
+ return mDownDelta;
+ }
+
+ /**
+ * @return whether the user has started dragging.
+ */
+ public boolean isDragging() {
+ return mIsDragging;
+ }
+
+ /**
+ * @return whether the user is currently interacting with the PiP.
+ */
+ public boolean isUserInteracting() {
+ return mIsUserInteracting;
+ }
+
+ /**
+ * @return whether the user has started dragging just in the last handled touch event.
+ */
+ public boolean startedDragging() {
+ return mStartedDragging;
+ }
+
+ /**
+ * @return Display ID of the last touch event.
+ */
+ public int getLastTouchDisplayId() {
+ return mLastTouchDisplayId;
+ }
+
+ /**
+ * Sets whether touching is currently allowed.
+ */
+ public void setAllowTouches(boolean allowTouches) {
+ mAllowTouches = allowTouches;
+
+ // If the user happens to touch down before this is sent from the system during a transition
+ // then block any additional handling by resetting the state now
+ if (mIsUserInteracting) {
+ reset();
+ }
+ }
+
+ /**
+ * Disallows dragging offscreen for the duration of the current gesture.
+ */
+ public void setDisallowDraggingOffscreen() {
+ mAllowDraggingOffscreen = false;
+ }
+
+ /**
+ * @return whether dragging offscreen is allowed during this gesture.
+ */
+ public boolean allowDraggingOffscreen() {
+ return mAllowDraggingOffscreen;
+ }
+
+ /**
+ * @return whether this gesture is a double-tap.
+ */
+ public boolean isDoubleTap() {
+ return mIsDoubleTap;
+ }
+
+ /**
+ * @return whether this gesture will potentially lead to a following double-tap.
+ */
+ public boolean isWaitingForDoubleTap() {
+ return mIsWaitingForDoubleTap;
+ }
+
+ /**
+ * Schedules the callback to run if the next double tap does not occur. Only runs if
+ * isWaitingForDoubleTap() is true.
+ */
+ public void scheduleDoubleTapTimeoutCallback() {
+ if (mIsWaitingForDoubleTap) {
+ long delay = getDoubleTapTimeoutCallbackDelay();
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
+ }
+ }
+
+ long getDoubleTapTimeoutCallbackDelay() {
+ if (mIsWaitingForDoubleTap) {
+ return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the timeout callback if it's in queue.
+ */
+ public void removeDoubleTapTimeoutCallback() {
+ mIsWaitingForDoubleTap = false;
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ }
+
+ void scheduleHoverExitTimeoutCallback() {
+ mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+ mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
+ }
+
+ void removeHoverExitTimeoutCallback() {
+ mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+ }
+
+ void addMovementToVelocityTracker(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ return;
+ }
+
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ private void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Dumps the {@link PipTouchState}.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
+ pw.println(innerPrefix + "mAllowInputEvents=" + mAllowInputEvents);
+ pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
+ pw.println(innerPrefix + "mLastTouchDisplayId=" + mLastTouchDisplayId);
+ pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
+ pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
+ pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
+ pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
+ pw.println(innerPrefix + "mVelocity=" + mVelocity);
+ pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
+ pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
+ pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
+ pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 235456c0f716..3b4fb9fbd8a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1069,7 +1069,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
- if (tasks == null || tasks.isEmpty()) {
+ if (tasks == null) {
return false;
}
for (int i = tasks.size() - 1; i >= 0; --i) {
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 708b14cdd19a..a0f9c6b9bb15 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
@@ -33,16 +33,8 @@ import static android.view.WindowInsets.Type.statusBars;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent;
-import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
-import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
-import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
-import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
@@ -858,26 +850,18 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
case MotionEvent.ACTION_UP: {
if (mTransitionDragActive) {
- final DesktopModeVisualIndicator.IndicatorType indicatorType =
- mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+ mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
mTransitionDragActive = false;
- if (indicatorType == TO_DESKTOP_INDICATOR
- || indicatorType == TO_SPLIT_LEFT_INDICATOR
- || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
- if (DesktopModeStatus.isEnabled()) {
- animateToDesktop(relevantDecor, ev);
- }
- mMoveToDesktopAnimator = null;
- return;
- } else if (mMoveToDesktopAnimator != null) {
+ if (mMoveToDesktopAnimator != null) {
// Though this isn't a hover event, we need to update handle's hover state
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
mDesktopTasksController.onDragPositioningEndThroughStatusBar(
new PointF(ev.getRawX(), ev.getRawY()),
relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
+ calculateFreeformBounds(ev.getDisplayId(),
+ DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
mMoveToDesktopAnimator = null;
return;
} else {
@@ -946,54 +930,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
(int) (screenHeight * (adjustmentPercentage + scale)));
}
- /**
- * Blocks relayout until transition is finished and transitions to Desktop
- */
- private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
- MotionEvent ev) {
- centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
- }
-
- /**
- * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
- * @param relevantDecor the window decor of the task to be animated
- * @param ev the motion event that triggers the animation
- * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
- * Currently fullscreen -> split snap still animates to center screen before readjusting.
- */
- private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
- MotionEvent ev) {
- ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl sc = relevantDecor.mTaskSurface;
- final Rect endBounds = calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE);
- final Transaction t = mTransactionFactory.get();
- final float diffX = endBounds.centerX() - ev.getRawX();
- final float diffY = endBounds.top - ev.getRawY();
- final float startingX = ev.getRawX() - DRAG_FREEFORM_SCALE
- * mDragToDesktopAnimationStartBounds.width() / 2;
-
- animator.addUpdateListener(animation -> {
- final float animatorValue = (float) animation.getAnimatedValue();
- final float x = startingX + diffX * animatorValue;
- final float y = ev.getRawY() + diffY * animatorValue;
- t.setPosition(sc, x, y);
- t.apply();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController
- .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
- }
- });
- animator.start();
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f790d2abcdb4..d0879434657d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -425,7 +425,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
boolean shouldResizeListenerHandleEvent(MotionEvent e, Point offset) {
- return mDragResizeListener.shouldHandleEvent(e, offset);
+ return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
}
boolean isHandlingDragResize() {
@@ -795,7 +795,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
private Region getGlobalExclusionRegion() {
Region exclusionRegion;
- if (mTaskInfo.isResizeable) {
+ if (mDragResizeListener != null && mTaskInfo.isResizeable) {
exclusionRegion = mDragResizeListener.getCornersRegion();
} else {
exclusionRegion = new Region();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index af055230b629..987aadfbdef2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -31,15 +31,16 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
private val animatedTaskWidth
get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+ val scale: Float
+ get() = dragToDesktopAnimator.animatedValue as Float
private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
DRAG_FREEFORM_SCALE)
.setDuration(ANIMATION_DURATION.toLong())
.apply {
val t = SurfaceControl.Transaction()
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
- addUpdateListener { animation ->
- val animatorValue = animation.animatedValue as Float
- t.setScale(taskSurface, animatorValue, animatorValue)
+ addUpdateListener {
+ t.setScale(taskSurface, scale, scale)
.setCornerRadius(taskSurface, cornerRadius)
.apply()
}
@@ -90,9 +91,9 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
}
/**
- * Ends the animation, setting the scale and position to the final animation value
+ * Cancels the animation, intended to be used when another animator will take over.
*/
- fun endAnimator() {
- dragToDesktopAnimator.end()
+ fun cancelAnimator() {
+ dragToDesktopAnimator.cancel()
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index b94989d98e97..12e395db8396 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -24,6 +24,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -143,6 +144,10 @@ class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
}
}
+ @FlakyTest(bugId = 293133362)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 4e9a9d65dbf9..9cc3a989894e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -179,12 +179,13 @@ object SplitScreenUtils {
val displayBounds =
wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
?: error("Display not found")
+ val swipeXCoordinate = displayBounds.centerX() / 2
// Pull down the notifications
device.swipe(
- displayBounds.centerX(),
+ swipeXCoordinate,
5,
- displayBounds.centerX(),
+ swipeXCoordinate,
displayBounds.bottom,
50 /* steps */
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 9f3a4d9c135d..0c45d52d5320 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
@@ -406,6 +407,31 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
assertThat(listener.stashedOnSecondaryDisplay).isTrue()
}
+ @Test
+ fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
+ val taskId = 1
+ repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
+ repo.removeFreeformTask(taskId)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+ }
+
+ @Test
+ fun saveBoundsBeforeMaximize_boundsSavedByTaskId() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMaximize(taskId, bounds)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isEqualTo(bounds)
+ }
+
+ @Test
+ fun removeBoundsBeforeMaximize_returnsNullAfterBoundsRemoved() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMaximize(taskId, bounds)
+ repo.removeBoundsBeforeMaximize(taskId)
+ assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
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 4fbf2bddb7b2..93a967e9bfc9 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
@@ -95,9 +95,10 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.kotlin.times
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -116,13 +117,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
- @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+ @Mock lateinit var toggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@@ -154,6 +156,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -179,7 +185,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
- mToggleResizeDesktopTaskTransitionHandler,
+ toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
desktopModeLoggerTransitionObserver,
@@ -929,15 +935,74 @@ class DesktopTasksControllerTest : ShellTestCase() {
controller.enterSplit(DEFAULT_DISPLAY, false)
verify(splitScreenController).requestEnterSplitSelect(
- task2,
- any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds
+ task2,
+ any(),
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ task2.configuration.windowConfiguration.bounds
)
}
- private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createFreeformTask(displayId)
+ @Test
+ fun toggleBounds_togglesToStableBounds() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(task)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
+ .isEqualTo(STABLE_BOUNDS)
+ }
+
+ @Test
+ fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(task)
+ assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId))
+ .isEqualTo(bounds)
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds)
+ .isEqualTo(boundsBeforeMaximize)
+ }
+
+ @Test
+ fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(task)
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(task)
+
+ // Assert last bounds before maximize removed after use
+ assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+ }
+
+ private fun setUpFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null
+ ): RunningTaskInfo {
+ val task = createFreeformTask(displayId, bounds)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId)
@@ -1004,6 +1069,18 @@ class DesktopTasksControllerTest : ShellTestCase() {
return arg.value
}
+ private fun getLatestToggleResizeDesktopTaskWct(): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ .startTransition(capture(arg))
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ }
+ return arg.value
+ }
+
private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
@@ -1042,6 +1119,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
companion object {
const val SECOND_DISPLAY = 2
+ private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 2f6f3207137d..52da7fb811d0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.Rect
import android.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -31,13 +32,17 @@ class DesktopTestHelpers {
/** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
@JvmStatic
@JvmOverloads
- fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ fun createFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null
+ ): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
.setDisplayId(displayId)
.setToken(MockToken().token())
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.setLastActiveTime(100)
+ .apply { bounds?.let { setBounds(it) }}
.build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 98e90d60b3b6..2ade3fba9b08 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -190,7 +190,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
handler.cancelDragToDesktopTransition()
// Cancel animation should run since it had already started.
- verify(dragAnimator).endAnimator()
+ verify(dragAnimator).cancelAnimator()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 3d5cd6939d1b..85f1da5322ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -16,33 +16,26 @@
package com.android.wm.shell.pip.phone;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
-import android.os.SystemProperties;
import android.testing.AndroidTestingRunner;
import android.util.Size;
import android.view.DisplayInfo;
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
import java.util.HashMap;
import java.util.Map;
@@ -63,15 +56,24 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase {
private static final float DEFAULT_PERCENT = 0.6f;
/** Minimum sizing percentage */
private static final float MIN_PERCENT = 0.5f;
+ /** Threshold to determine if a Display is square-ish. */
+ private static final float SQUARE_DISPLAY_THRESHOLD = 0.95f;
+ /** Default sizing percentage for square-ish Display. */
+ private static final float SQUARE_DISPLAY_DEFAULT_PERCENT = 0.5f;
+ /** Minimum sizing percentage for square-ish Display. */
+ private static final float SQUARE_DISPLAY_MIN_PERCENT = 0.4f;
/** Aspect ratio that the new PIP size spec logic optimizes for. */
private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
- /** A map of aspect ratios to be tested to expected sizes */
- private static Map<Float, Size> sExpectedMaxSizes;
- private static Map<Float, Size> sExpectedDefaultSizes;
- private static Map<Float, Size> sExpectedMinSizes;
- /** A static mockito session object to mock {@link SystemProperties} */
- private static StaticMockitoSession sStaticMockitoSession;
+ /** Maps of aspect ratios to be tested to expected sizes on non-square Display. */
+ private static Map<Float, Size> sNonSquareDisplayExpectedMaxSizes;
+ private static Map<Float, Size> sNonSquareDisplayExpectedDefaultSizes;
+ private static Map<Float, Size> sNonSquareDisplayExpectedMinSizes;
+
+ /** Maps of aspect ratios to be tested to expected sizes on square Display. */
+ private static Map<Float, Size> sSquareDisplayExpectedMaxSizes;
+ private static Map<Float, Size> sSquareDisplayExpectedDefaultSizes;
+ private static Map<Float, Size> sSquareDisplayExpectedMinSizes;
@Mock private Context mContext;
@Mock private Resources mResources;
@@ -80,49 +82,55 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase {
private SizeSpecSource mSizeSpecSource;
/**
- * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+ * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ * This is to initialize the expectations on non-square Display only.
*/
- private static void setUpStaticSystemPropertiesSession() {
- sStaticMockitoSession = mockitoSession()
- .mockStatic(SystemProperties.class).startMocking();
- when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
- String property = invocation.getArgument(0);
- if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
- return Float.toString(DEFAULT_PERCENT);
- } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
- return Float.toString(MIN_PERCENT);
- }
-
- // throw an exception if illegal arguments are used for these tests
- throw new InvalidUseOfMatchersException(
- String.format("Argument %s does not match", property)
- );
- });
+ private static void initNonSquareDisplayExpectedSizes() {
+ sNonSquareDisplayExpectedMaxSizes = new HashMap<>();
+ sNonSquareDisplayExpectedDefaultSizes = new HashMap<>();
+ sNonSquareDisplayExpectedMinSizes = new HashMap<>();
+
+ sNonSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sNonSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+ sNonSquareDisplayExpectedMinSizes.put(16f / 9, new Size(501, 282));
+
+ sNonSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+ sNonSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
+ sNonSquareDisplayExpectedMinSizes.put(4f / 3, new Size(447, 335));
+
+ sNonSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+ sNonSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
+ sNonSquareDisplayExpectedMinSizes.put(3f / 4, new Size(335, 447));
+
+ sNonSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+ sNonSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
+ sNonSquareDisplayExpectedMinSizes.put(9f / 16, new Size(282, 501));
}
/**
* Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ * This is to initialize the expectations on square Display only.
*/
- private static void initExpectedSizes() {
- sExpectedMaxSizes = new HashMap<>();
- sExpectedDefaultSizes = new HashMap<>();
- sExpectedMinSizes = new HashMap<>();
-
- sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
- sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
- sExpectedMinSizes.put(16f / 9, new Size(501, 282));
-
- sExpectedMaxSizes.put(4f / 3, new Size(893, 670));
- sExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
- sExpectedMinSizes.put(4f / 3, new Size(447, 335));
-
- sExpectedMaxSizes.put(3f / 4, new Size(670, 893));
- sExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
- sExpectedMinSizes.put(3f / 4, new Size(335, 447));
-
- sExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
- sExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
- sExpectedMinSizes.put(9f / 16, new Size(282, 501));
+ private static void initSquareDisplayExpectedSizes() {
+ sSquareDisplayExpectedMaxSizes = new HashMap<>();
+ sSquareDisplayExpectedDefaultSizes = new HashMap<>();
+ sSquareDisplayExpectedMinSizes = new HashMap<>();
+
+ sSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(500, 281));
+ sSquareDisplayExpectedMinSizes.put(16f / 9, new Size(400, 225));
+
+ sSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+ sSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(447, 335));
+ sSquareDisplayExpectedMinSizes.put(4f / 3, new Size(357, 268));
+
+ sSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+ sSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(335, 447));
+ sSquareDisplayExpectedMinSizes.put(3f / 4, new Size(268, 357));
+
+ sSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+ sSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(282, 501));
+ sSquareDisplayExpectedMinSizes.put(9f / 16, new Size(225, 400));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
@@ -137,20 +145,38 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase {
@Before
public void setUp() {
- initExpectedSizes();
-
- when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
- when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
- when(mResources.getString(anyInt())).thenReturn("0x0");
+ initNonSquareDisplayExpectedSizes();
+ initSquareDisplayExpectedSizes();
+
+ when(mResources.getFloat(R.dimen.config_pipSystemPreferredDefaultSizePercent))
+ .thenReturn(DEFAULT_PERCENT);
+ when(mResources.getFloat(R.dimen.config_pipSystemPreferredMinimumSizePercent))
+ .thenReturn(MIN_PERCENT);
+ when(mResources.getDimensionPixelSize(R.dimen.default_minimal_size_pip_resizable_task))
+ .thenReturn(DEFAULT_MIN_EDGE_SIZE);
+ when(mResources.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio))
+ .thenReturn(OPTIMIZED_ASPECT_RATIO);
+ when(mResources.getString(R.string.config_defaultPictureInPictureScreenEdgeInsets))
+ .thenReturn("0x0");
when(mResources.getDisplayMetrics())
.thenReturn(getContext().getResources().getDisplayMetrics());
+ when(mResources.getFloat(R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize))
+ .thenReturn(SQUARE_DISPLAY_THRESHOLD);
+ when(mResources.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay))
+ .thenReturn(SQUARE_DISPLAY_DEFAULT_PERCENT);
+ when(mResources.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay))
+ .thenReturn(SQUARE_DISPLAY_MIN_PERCENT);
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
+ }
+ private void setupSizeSpecWithDisplayDimension(int width, int height) {
DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
- displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+ displayInfo.logicalWidth = width;
+ displayInfo.logicalHeight = height;
// use the parent context (not the mocked one) to obtain the display layout
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
@@ -159,38 +185,57 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase {
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
- setUpStaticSystemPropertiesSession();
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
mSizeSpecSource.setOverrideMinSize(null);
}
- @After
- public void cleanUp() {
- sStaticMockitoSession.finishMocking();
+ @Test
+ public void testGetMaxSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedMaxSizes,
+ (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetDefaultSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedDefaultSizes,
+ (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetMinSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedMinSizes,
+ (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
}
@Test
- public void testGetMaxSize() {
- forEveryTestCaseCheck(sExpectedMaxSizes,
+ public void testGetMaxSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedMaxSizes,
(aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
}
@Test
- public void testGetDefaultSize() {
- forEveryTestCaseCheck(sExpectedDefaultSizes,
+ public void testGetDefaultSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedDefaultSizes,
(aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
}
@Test
- public void testGetMinSize() {
- forEveryTestCaseCheck(sExpectedMinSizes,
+ public void testGetMinSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedMinSizes,
(aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
}
@Test
public void testGetSizeForAspectRatio_noOverrideMinSize() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
// an initial size with 16:9 aspect ratio
Size initSize = new Size(600, 337);
@@ -202,6 +247,7 @@ public class PhoneSizeSpecSourceTest extends ShellTestCase {
@Test
public void testGetSizeForAspectRatio_withOverrideMinSize() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
// an initial size with a 1:1 aspect ratio
Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
mSizeSpecSource.setOverrideMinSize(initSize);
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index d7b5914130ee..9d4b426a6759 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -304,7 +304,7 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
_ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
const int32_t error = ExtractEntryToFile(mHandle, &(zipEntry->entry), fd);
if (error) {
- ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+ ALOGW("ExtractToFile failed with %s", ErrorCodeString(error));
return false;
}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index 4dafd0aa6df4..42547832133b 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,7 +27,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
- "animationlib",
+ "//frameworks/libs/systemui:animationlib",
"frameworks-base-testutils",
"junit",
"kotlinx_coroutines_test",
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 341c3e8cf373..29bb1b9846b4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -31,6 +31,7 @@ license {
aconfig_declarations {
name: "hwui_flags",
package: "com.android.graphics.hwui.flags",
+ container: "system",
srcs: [
"aconfig/hwui_flags.aconfig",
],
@@ -78,13 +79,13 @@ cc_defaults {
include_dirs: [
"external/skia/include/private",
"external/skia/src/core",
+ "external/skia/src/utils",
],
target: {
android: {
include_dirs: [
"external/skia/src/image",
- "external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
],
@@ -529,7 +530,9 @@ cc_defaults {
"effects/GainmapRenderer.cpp",
"pipeline/skia/BackdropFilterDrawable.cpp",
"pipeline/skia/HolePunch.cpp",
+ "pipeline/skia/SkiaCpuPipeline.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
+ "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/StretchMask.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
@@ -568,6 +571,7 @@ cc_defaults {
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"Mesh.cpp",
@@ -604,9 +608,9 @@ cc_defaults {
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
+ "pipeline/skia/SkiaGpuPipeline.cpp",
"pipeline/skia/SkiaMemoryTracer.cpp",
"pipeline/skia/SkiaOpenGLPipeline.cpp",
- "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaProfileRenderer.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
@@ -630,7 +634,6 @@ cc_defaults {
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
"Layer.cpp",
- "LayerUpdateQueue.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ec53070f6cb8..c1510d96461f 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -242,7 +242,7 @@ enum class ProfileType { None, Console, Bars };
enum class OverdrawColorSet { Default = 0, Deuteranomaly };
-enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 };
enum class StretchEffectBehavior {
ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 659bcdc6852d..50f8b3929e1e 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.graphics.hwui.flags"
+container: "system"
flag {
name: "clip_shader"
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
new file mode 100644
index 000000000000..5bbbc1009541
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pipeline/skia/SkiaCpuPipeline.h"
+
+#include <system/window.h>
+
+#include "DeviceInfo.h"
+#include "LightingInfo.h"
+#include "renderthread/Frame.h"
+#include "utils/Color.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+ if (!rendered) {
+ return;
+ }
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ node->setLayerSurface(SkSurfaces::Raster(info, &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+MakeCurrentResult SkiaCpuPipeline::makeCurrent() {
+ return MakeCurrentResult::AlreadyCurrent;
+}
+
+Frame SkiaCpuPipeline::getFrame() {
+ return Frame(mSurface->width(), mSurface->height(), 0);
+}
+
+IRenderPipeline::DrawResult SkiaCpuPipeline::draw(
+ const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
+ LightingInfo::updateLighting(lightGeometry, lightInfo);
+ renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface,
+ SkMatrix::I());
+ return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
+}
+
+bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+ if (surface) {
+ ANativeWindowBuffer* buffer;
+ surface->dequeueBuffer(surface, &buffer, nullptr);
+ int width, height;
+ surface->query(surface, NATIVE_WINDOW_WIDTH, &width);
+ surface->query(surface, NATIVE_WINDOW_HEIGHT, &height);
+ SkImageInfo imageInfo =
+ SkImageInfo::Make(width, height, mSurfaceColorType,
+ SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace);
+ size_t widthBytes = width * imageInfo.bytesPerPixel();
+ void* pixels = buffer->reserved[0];
+ mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes);
+ } else {
+ mSurface = sk_sp<SkSurface>();
+ }
+ return true;
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
new file mode 100644
index 000000000000..5a1014c2c2de
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaCpuPipeline : public SkiaPipeline {
+public:
+ SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaCpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override;
+ renderthread::Frame getFrame() override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return mSurface.get() != nullptr; }
+ bool isContextReady() override { return true; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+
+private:
+ sk_sp<SkSurface> mSurface;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
new file mode 100644
index 000000000000..7bfbfdc4b96b
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+#include <SkImageAndroid.h>
+#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {}
+
+SkiaGpuPipeline::~SkiaGpuPipeline() {
+ unpinImages();
+}
+
+void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ sk_sp<GrDirectContext> cachedContext;
+
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+ if (!rendered) {
+ return;
+ }
+ // cache the current context so that we can defer flushing it until
+ // either all the layers have been rendered or the context changes
+ GrDirectContext* currentContext =
+ GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
+ if (cachedContext.get() != currentContext) {
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers (context changed)");
+ cachedContext->flushAndSubmit();
+ }
+ cachedContext.reset(SkSafeRef(currentContext));
+ }
+ }
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers");
+ cachedContext->flushAndSubmit();
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
+ if (!mRenderThread.getGrContext()) {
+ ALOGD("Trying to pin an image with an invalid GrContext");
+ return false;
+ }
+ for (SkImage* image : mutableImages) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
+ mPinnedImages.emplace_back(sk_ref_sp(image));
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SkiaGpuPipeline::unpinImages() {
+ for (auto& image : mPinnedImages) {
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
+ }
+ mPinnedImages.clear();
+}
+
+void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ GrDirectContext* context = thread.getGrContext();
+ if (context && !bitmap->isHardware()) {
+ ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
+ auto image = bitmap->makeImage();
+ if (image.get()) {
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
+ // A submit is necessary as there may not be a frame coming soon, so without a call
+ // to submit these texture uploads can just sit in the queue building up until
+ // we run out of RAM
+ context->flushAndSubmit();
+ }
+ }
+}
+
+sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
+void SkiaGpuPipeline::dumpResourceCacheUsage() const {
+ int resources;
+ size_t bytes;
+ mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
+ size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
+
+ SkString log("Resource Cache Usage:\n");
+ log.appendf("%8d items\n", resources);
+ log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
+ bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
+
+ ALOGD("%s", log.c_str());
+}
+
+void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c8d598702a7c..e4b1f916b4d6 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-#include "SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
#include <cutils/properties.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <strings.h>
#include "DeferredLayerUpdater.h"
#include "FrameInfo.h"
-#include "LayerDrawable.h"
#include "LightingInfo.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
#include "private/hwui/DrawGlInfo.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
@@ -47,7 +47,7 @@ namespace uirenderer {
namespace skiapipeline {
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
- : SkiaPipeline(thread), mEglManager(thread.eglManager()) {
+ : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 99469d1e3628..34932b1b1e25 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaPipeline.h"
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
@@ -40,6 +37,9 @@
#include <SkTypeface.h>
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/encode/SkPngEncoder.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
#include <sstream>
@@ -62,37 +62,13 @@ SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
setSurfaceColorProperties(mColorMode);
}
-SkiaPipeline::~SkiaPipeline() {
- unpinImages();
-}
+SkiaPipeline::~SkiaPipeline() {}
void SkiaPipeline::onDestroyHardwareResources() {
unpinImages();
mRenderThread.cacheManager().trimStaleResources();
}
-bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
- if (!mRenderThread.getGrContext()) {
- ALOGD("Trying to pin an image with an invalid GrContext");
- return false;
- }
- for (SkImage* image : mutableImages) {
- if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
- mPinnedImages.emplace_back(sk_ref_sp(image));
- } else {
- return false;
- }
- }
- return true;
-}
-
-void SkiaPipeline::unpinImages() {
- for (auto& image : mPinnedImages) {
- skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
- }
- mPinnedImages.clear();
-}
-
void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const LightInfo& lightInfo) {
@@ -102,136 +78,48 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
layerUpdateQueue->clear();
}
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- sk_sp<GrDirectContext> cachedContext;
-
- // Render all layers that need to be updated, in order.
- for (size_t i = 0; i < layers.entries().size(); i++) {
- RenderNode* layerNode = layers.entries()[i].renderNode.get();
- // only schedule repaint if node still on layer - possible it may have been
- // removed during a dropped frame, but layers may still remain scheduled so
- // as not to lose info on what portion is damaged
- if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
- continue;
- }
- SkASSERT(layerNode->getLayerSurface());
- SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
- if (!displayList || displayList->isEmpty()) {
- ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
- return;
- }
-
- const Rect& layerDamage = layers.entries()[i].damage;
-
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
- int saveCount = layerCanvas->save();
- SkASSERT(saveCount == 1);
-
- layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
- // TODO: put localized light center calculation and storage to a drawable related code.
- // It does not seem right to store something localized in a global state
- // fix here and in recordLayers
- const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
- Vector3 transformedLightCenter(savedLightCenter);
- // map current light center into RenderNode's coordinate space
- layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
- LightingInfo::setLightCenterRaw(transformedLightCenter);
-
- const RenderProperties& properties = layerNode->properties();
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
- return;
- }
-
- ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
- bounds.height());
+bool SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) {
+ SkASSERT(layerNode->getLayerSurface());
+ SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
+ if (!displayList || displayList->isEmpty()) {
+ ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+ return false;
+ }
- layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
- layerCanvas->clear(SK_ColorTRANSPARENT);
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
- RenderNodeDrawable root(layerNode, layerCanvas, false);
- root.forceDraw(layerCanvas);
- layerCanvas->restoreToCount(saveCount);
+ int saveCount = layerCanvas->save();
+ SkASSERT(saveCount == 1);
- LightingInfo::setLightCenterRaw(savedLightCenter);
+ layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
- // cache the current context so that we can defer flushing it until
- // either all the layers have been rendered or the context changes
- GrDirectContext* currentContext =
- GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
- if (cachedContext.get() != currentContext) {
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers (context changed)");
- cachedContext->flushAndSubmit();
- }
- cachedContext.reset(SkSafeRef(currentContext));
- }
+ // TODO: put localized light center calculation and storage to a drawable related code.
+ // It does not seem right to store something localized in a global state
+ // fix here and in recordLayers
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ Vector3 transformedLightCenter(savedLightCenter);
+ // map current light center into RenderNode's coordinate space
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+ const RenderProperties& properties = layerNode->properties();
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+ return false;
}
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers");
- cachedContext->flushAndSubmit();
- }
-}
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+ bounds.height());
-bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) {
- // compute the size of the surface (i.e. texture) to be allocated for this layer
- const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
- const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
-
- SkSurface* layer = node->getLayerSurface();
- if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
- SkImageInfo info;
- info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
- kPremul_SkAlphaType, getSurfaceColorSpace());
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
- SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
- if (node->getLayerSurface()) {
- // update the transform in window of the layer to reset its origin wrt light source
- // position
- Matrix4 windowTransform;
- damageAccumulator.computeCurrentTransform(&windowTransform);
- node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
- } else {
- String8 cachesOutput;
- mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
- &mRenderThread.renderState());
- ALOGE("%s", cachesOutput.c_str());
- if (errorHandler) {
- std::ostringstream err;
- err << "Unable to create layer for " << node->getName();
- const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
- err << ", size " << info.width() << "x" << info.height() << " max size "
- << maxTextureSize << " color type " << (int)info.colorType() << " has context "
- << (int)(mRenderThread.getGrContext() != nullptr);
- errorHandler->onError(err.str());
- }
- }
- return true;
- }
- return false;
-}
+ layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+ layerCanvas->clear(SK_ColorTRANSPARENT);
-void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- GrDirectContext* context = thread.getGrContext();
- if (context && !bitmap->isHardware()) {
- ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
- auto image = bitmap->makeImage();
- if (image.get()) {
- skgpu::ganesh::PinAsTexture(context, image.get());
- skgpu::ganesh::UnpinTexture(context, image.get());
- // A submit is necessary as there may not be a frame coming soon, so without a call
- // to submit these texture uploads can just sit in the queue building up until
- // we run out of RAM
- context->flushAndSubmit();
- }
- }
+ RenderNodeDrawable root(layerNode, layerCanvas, false);
+ root.forceDraw(layerCanvas);
+ layerCanvas->restoreToCount(saveCount);
+
+ LightingInfo::setLightCenterRaw(savedLightCenter);
+ return true;
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
@@ -599,45 +487,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip,
}
}
-void SkiaPipeline::dumpResourceCacheUsage() const {
- int resources;
- size_t bytes;
- mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
- size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
-
- SkString log("Resource Cache Usage:\n");
- log.appendf("%8d items\n", resources);
- log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
- bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
-
- ALOGD("%s", log.c_str());
-}
-
-void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
- if (mHardwareBuffer) {
- AHardwareBuffer_release(mHardwareBuffer);
- mHardwareBuffer = nullptr;
- }
-
- if (buffer) {
- AHardwareBuffer_acquire(buffer);
- mHardwareBuffer = buffer;
- }
-}
-
-sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams) {
- auto bufferColorSpace = bufferParams.getColorSpace();
- if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
- !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
- mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
- bufferColorSpace, nullptr, true);
- mBufferColorSpace = bufferColorSpace;
- }
- return mBufferSurface;
-}
-
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index befee8989383..cf14b1f9ebe3 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -42,18 +42,9 @@ public:
void onDestroyHardwareResources() override;
- bool pinImages(std::vector<SkImage*>& mutableImages) override;
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override;
-
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override;
-
void setSurfaceColorProperties(ColorMode colorMode) override;
SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
@@ -63,9 +54,8 @@ public:
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform);
- static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
-
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+ bool renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage);
+ virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0;
// Sets the recording callback to the provided function and the recording mode
// to CallbackAPI
@@ -75,19 +65,11 @@ public:
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
- virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
- bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
-
void setTargetSdrHdrRatio(float ratio) override;
protected:
- sk_sp<SkSurface> getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams);
- void dumpResourceCacheUsage() const;
-
renderthread::RenderThread& mRenderThread;
- AHardwareBuffer* mHardwareBuffer = nullptr;
sk_sp<SkSurface> mBufferSurface = nullptr;
sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
@@ -125,8 +107,6 @@ private:
// Set up a multi frame capture.
bool setupMultiFrameCapture();
- std::vector<sk_sp<SkImage>> mPinnedImages;
-
// Block of properties used only for debugging to record a SkPicture and save it in a file.
// There are three possible ways of recording drawing commands.
enum class CaptureMode {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index fd0a8e06f39c..d06dba05ee88 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "SkiaVulkanPipeline.h"
+#include "pipeline/skia/SkiaVulkanPipeline.h"
#include <GrDirectContext.h>
#include <GrTypes.h>
@@ -28,10 +28,10 @@
#include "DeferredLayerUpdater.h"
#include "LightingInfo.h"
#include "Readback.h"
-#include "ShaderCache.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
-#include "VkInteropFunctorDrawable.h"
+#include "pipeline/skia/ShaderCache.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
+#include "pipeline/skia/VkInteropFunctorDrawable.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
#include "renderthread/IRenderPipeline.h"
@@ -42,7 +42,8 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {
+SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
+ : SkiaGpuPipeline(thread) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 000000000000..9159eae46065
--- /dev/null
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread);
+ virtual ~SkiaGpuPipeline();
+
+ virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override;
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
+
+protected:
+ sk_sp<SkSurface> getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
+ void dumpResourceCacheUsage() const;
+
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+
+private:
+ std::vector<sk_sp<SkImage>> mPinnedImages;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
index ebe8b6e15d44..6e7478288777 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,7 +19,7 @@
#include <EGL/egl.h>
#include <system/window.h>
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
@@ -30,7 +30,7 @@ class Bitmap;
namespace uirenderer {
namespace skiapipeline {
-class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
SkiaOpenGLPipeline(renderthread::RenderThread& thread);
virtual ~SkiaOpenGLPipeline();
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
index 624eaa51a584..0d30df48baee 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
@@ -17,7 +17,7 @@
#pragma once
#include "SkRefCnt.h"
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
@@ -30,7 +30,7 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
explicit SkiaVulkanPipeline(renderthread::RenderThread& thread);
virtual ~SkiaVulkanPipeline();
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
new file mode 120000
index 000000000000..4fb4784f9f60
--- /dev/null
+++ b/libs/hwui/platform/host/android/api-level.h
@@ -0,0 +1 @@
+../../../../../../../bionic/libc/include/android/api-level.h \ No newline at end of file
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 000000000000..a71726585081
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+#include "renderthread/Frame.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaGpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override {
+ return false;
+ }
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {}
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override {
+ return renderthread::MakeCurrentResult::Failed;
+ }
+ renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); }
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override {
+ return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)};
+ }
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override {
+ return false;
+ }
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return false; }
+ bool isContextReady() override { return false; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
new file mode 100644
index 000000000000..4fafbcc4748d
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaOpenGLPipeline : public SkiaGpuPipeline {
+public:
+ SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
new file mode 100644
index 000000000000..d54caef45bb5
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanPipeline : public SkiaGpuPipeline {
+public:
+ SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1fbd580874f6..22de2f29792d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,8 +35,8 @@
#include "Properties.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "thread/CommonPool.h"
#include "utils/GLUtils.h"
@@ -108,7 +108,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor)
}
void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
+ skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap);
}
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index b8c3a4de2bd4..ee1d1f8789d9 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -30,8 +30,6 @@
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
-class GrDirectContext;
-
struct ANativeWindow;
namespace android {
@@ -94,7 +92,6 @@ public:
virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
- virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index eaafa595ae4f..6d84e7066839 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -836,7 +836,7 @@ public final class FadeManagerConfiguration implements Parcelable {
*/
public Builder(@NonNull FadeManagerConfiguration fmc) {
mFadeState = fmc.mFadeState;
- mUsageToFadeWrapperMap = fmc.mUsageToFadeWrapperMap.clone();
+ copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap);
mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
fmc.mAttrToFadeWrapperMap);
mFadeableUsages = fmc.mFadeableUsages.clone();
@@ -1459,6 +1459,14 @@ public final class FadeManagerConfiguration implements Parcelable {
}
}
+ private void copyUsageToFadeWrapperMapInternal(
+ SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) {
+ for (int index = 0; index < usageToFadeWrapperMap.size(); index++) {
+ mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index),
+ new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index)));
+ }
+ }
+
private void validateFadeState(int state) {
switch(state) {
case FADE_STATE_DISABLED:
@@ -1551,6 +1559,12 @@ public final class FadeManagerConfiguration implements Parcelable {
FadeVolumeShaperConfigsWrapper() {}
+ FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) {
+ Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null");
+ this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig;
+ this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig;
+ }
+
public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
mFadeOutVolShaperConfig = fadeOutConfig;
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 82d43bcaaec9..ce7474c024a3 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2364,12 +2364,16 @@ final public class MediaCodec {
}
// at the moment no codecs support detachable surface
+ boolean canDetach = GetFlag(() -> android.media.codec.Flags.nullOutputSurfaceSupport());
if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
// Detached surface flag is only meaningful if surface is null. Otherwise, it is
// ignored.
- if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
+ if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0 && !canDetach) {
throw new IllegalArgumentException("Codec does not support detached surface");
}
+ } else {
+ // don't allow detaching if API is disabled
+ canDetach = false;
}
String[] keys = null;
@@ -2411,6 +2415,14 @@ final public class MediaCodec {
}
native_configure(keys, values, surface, crypto, descramblerBinder, flags);
+
+ if (canDetach) {
+ // If we were able to configure native codec with a detached surface
+ // we now know that we have a surface.
+ if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
+ mHasSurface = true;
+ }
+ }
}
/**
@@ -2455,12 +2467,19 @@ final public class MediaCodec {
if (!mHasSurface) {
throw new IllegalStateException("codec was not configured for an output surface");
}
+
// note: we still have a surface in detached mode, so keep mHasSurface
// we also technically allow calling detachOutputSurface multiple times in a row
- throw new IllegalStateException("codec does not support detaching output surface");
- // native_detachSurface();
+
+ if (GetFlag(() -> android.media.codec.Flags.nullOutputSurfaceSupport())) {
+ native_detachOutputSurface();
+ } else {
+ throw new IllegalStateException("codec does not support detaching output surface");
+ }
}
+ private native void native_detachOutputSurface();
+
/**
* Create a persistent input surface that can be used with codecs that normally have an input
* surface, such as video encoders. A persistent input can be reused by subsequent
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index dece6bdbb35f..ec95279f48f1 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -397,8 +397,14 @@ public class MediaDescription implements Parcelable {
* @return a new media description.
*/
public MediaDescription build() {
- return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, mIcon, mIconUri,
- mExtras, mMediaUri);
+ if (com.android.media.performance.flags.Flags.mediaDescriptionAshmemBitmap()) {
+ Bitmap icon = mIcon != null ? mIcon.asShared() : null;
+ return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, icon,
+ mIconUri, mExtras, mMediaUri);
+ } else {
+ return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, mIcon,
+ mIconUri, mExtras, mMediaUri);
+ }
}
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3f9440b60202..5aa006bce000 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1309,18 +1309,24 @@ public final class MediaRouter2 {
return;
}
- RoutingController newController;
- if (sessionInfo.isSystemSession()) {
- newController = getSystemController();
- newController.setRoutingSessionInfo(sessionInfo);
+ RoutingController newController = addRoutingController(sessionInfo);
+ notifyTransfer(oldController, newController);
+ }
+
+ @NonNull
+ private RoutingController addRoutingController(@NonNull RoutingSessionInfo session) {
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ // mSystemController is never released, so we only need to update its status.
+ mSystemController.setRoutingSessionInfo(session);
+ controller = mSystemController;
} else {
- newController = new RoutingController(sessionInfo);
+ controller = new RoutingController(session);
synchronized (mLock) {
- mNonSystemRoutingControllers.put(newController.getId(), newController);
+ mNonSystemRoutingControllers.put(controller.getId(), controller);
}
}
-
- notifyTransfer(oldController, newController);
+ return controller;
}
void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1329,40 +1335,12 @@ public final class MediaRouter2 {
return;
}
- if (sessionInfo.isSystemSession()) {
- // The session info is sent from SystemMediaRoute2Provider.
- RoutingController systemController = getSystemController();
- systemController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(systemController);
- return;
- }
-
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
- }
-
- if (matchingController == null) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Matching controller not found. uniqueSessionId="
- + sessionInfo.getId());
- return;
- }
-
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
+ RoutingController controller =
+ getMatchingController(sessionInfo, /* logPrefix */ "updateControllerOnHandler");
+ if (controller != null) {
+ controller.setRoutingSessionInfo(sessionInfo);
+ notifyControllerUpdated(controller);
}
-
- matchingController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(matchingController);
}
void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1371,34 +1349,47 @@ public final class MediaRouter2 {
return;
}
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
+ RoutingController matchingController =
+ getMatchingController(sessionInfo, /* logPrefix */ "releaseControllerOnHandler");
+
+ if (matchingController != null) {
+ matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
+ }
- if (matchingController == null) {
- if (DEBUG) {
- Log.d(
+ @Nullable
+ private RoutingController getMatchingController(
+ RoutingSessionInfo sessionInfo, String logPrefix) {
+ if (sessionInfo.isSystemSession()) {
+ return getSystemController();
+ } else {
+ RoutingController controller;
+ synchronized (mLock) {
+ controller = mNonSystemRoutingControllers.get(sessionInfo.getId());
+ }
+
+ if (controller == null) {
+ Log.w(
TAG,
- "releaseControllerOnHandler: Matching controller not found. "
- + "uniqueSessionId="
+ logPrefix
+ + ": Matching controller not found. uniqueSessionId="
+ sessionInfo.getId());
+ return null;
}
- return;
- }
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "releaseControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
+ RoutingSessionInfo oldInfo = controller.getRoutingSessionInfo();
+ if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(
+ TAG,
+ logPrefix
+ + ": Provider IDs are not matched. old="
+ + oldInfo.getProviderId()
+ + ", new="
+ + sessionInfo.getProviderId());
+ return null;
+ }
+ return controller;
}
-
- matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
void onRequestCreateControllerByManagerOnHandler(
@@ -3129,20 +3120,8 @@ public final class MediaRouter2 {
private void onTransferred(
@NonNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession) {
- if (!oldSession.isSystemSession()
- && !TextUtils.equals(
- getClientPackageName(), oldSession.getClientPackageName())) {
- return;
- }
-
- if (!newSession.isSystemSession()
- && !TextUtils.equals(
- getClientPackageName(), newSession.getClientPackageName())) {
- return;
- }
-
- // For successful in-session transfer, onControllerUpdated() handles it.
- if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+ if (!isSessionRelatedToTargetPackageName(oldSession)
+ || !isSessionRelatedToTargetPackageName(newSession)) {
return;
}
@@ -3169,16 +3148,14 @@ public final class MediaRouter2 {
private void onTransferFailed(
@NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
- if (!session.isSystemSession()
- && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+ if (!isSessionRelatedToTargetPackageName(session)) {
return;
}
notifyTransferFailure(route);
}
private void onSessionUpdated(@NonNull RoutingSessionInfo session) {
- if (!session.isSystemSession()
- && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+ if (!isSessionRelatedToTargetPackageName(session)) {
return;
}
@@ -3193,6 +3170,15 @@ public final class MediaRouter2 {
notifyControllerUpdated(controller);
}
+ /**
+ * Returns {@code true} if the session is a system session or if its client package name
+ * matches the proxy router's target package name.
+ */
+ private boolean isSessionRelatedToTargetPackageName(@NonNull RoutingSessionInfo session) {
+ return session.isSystemSession()
+ || TextUtils.equals(getClientPackageName(), session.getClientPackageName());
+ }
+
private void onSessionCreatedOnHandler(
int requestId, @NonNull RoutingSessionInfo sessionInfo) {
MediaRouter2Manager.TransferRequest matchingRequest = null;
@@ -3237,19 +3223,19 @@ public final class MediaRouter2 {
}
}
- private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo sessionInfo) {
+ private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo updatedSession) {
for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
String sessionId = request.mOldSessionInfo.getId();
- if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+ if (!TextUtils.equals(sessionId, updatedSession.getId())) {
continue;
}
- if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+
+ if (updatedSession.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
mTransferRequests.remove(request);
- this.onTransferred(request.mOldSessionInfo, sessionInfo);
break;
}
}
- this.onSessionUpdated(sessionInfo);
+ this.onSessionUpdated(updatedSession);
}
private void onSessionReleasedOnHandler(@NonNull RoutingSessionInfo session) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c1be6b5473d4..91c4f118658a 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -25,13 +25,6 @@ flag {
}
flag {
- name: "disable_screen_off_broadcast_receiver"
- namespace: "media_solutions"
- description: "Disables the broadcast receiver that prevents scanning when the screen is off."
- bug: "304234628"
-}
-
-flag {
name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
namespace: "media_solutions"
description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
@@ -108,6 +101,16 @@ flag {
}
flag {
+ name: "enable_mr2_service_non_main_bg_thread"
+ namespace: "media_solutions"
+ description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
+ bug: "310145678"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_screen_off_scanning"
is_exported: true
namespace: "media_solutions"
@@ -121,3 +124,13 @@ flag {
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
bug: "185136506"
}
+
+flag {
+ name: "enable_prevention_of_manager_scans_when_no_apps_scan"
+ namespace: "media_solutions"
+ description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
+ bug: "319604673"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/media/java/android/media/flags/performance.aconfig b/media/java/android/media/flags/performance.aconfig
new file mode 100644
index 000000000000..9e9197ef5039
--- /dev/null
+++ b/media/java/android/media/flags/performance.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.media.performance.flags"
+
+flag {
+ name: "media_description_ashmem_bitmap"
+ namespace: "systemui"
+ description: "Use ashmem to pass bitmaps in MediaDescription to avoid excessive Bitmap copies."
+ bug: "288241280"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 207ccbee0b50..871e9ab87299 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -80,4 +80,7 @@ interface ISessionManager {
boolean hasCustomMediaSessionPolicyProvider(String componentName);
int getSessionPolicies(in MediaSession.Token token);
void setSessionPolicies(in MediaSession.Token token, int policies);
+
+ // For testing of temporarily engaged sessions.
+ void expireTempEngagedSessions();
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 0fc80dd55fa9..82561f982a03 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -389,6 +389,14 @@ status_t JMediaCodec::setSurface(
return err;
}
+status_t JMediaCodec::detachOutputSurface() {
+ status_t err = mCodec->detachOutputSurface();
+ if (err == OK) {
+ mSurfaceTextureClient.clear();
+ }
+ return err;
+}
+
status_t JMediaCodec::createInputSurface(
sp<IGraphicBufferProducer>* bufferProducer) {
return mCodec->createInputSurface(bufferProducer);
@@ -1798,6 +1806,20 @@ static void android_media_MediaCodec_native_setSurface(
throwExceptionAsNecessary(env, err, codec);
}
+static void android_media_MediaCodec_native_detachOutputSurface(
+ JNIEnv *env,
+ jobject thiz) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL || codec->initCheck() != OK) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+ return;
+ }
+
+ status_t err = codec->detachOutputSurface();
+ throwExceptionAsNecessary(env, err, codec);
+}
+
sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface(
JNIEnv* env, jobject object) {
sp<PersistentSurface> persistentSurface;
@@ -4107,6 +4129,10 @@ static const JNINativeMethod gMethods[] = {
"(Landroid/view/Surface;)V",
(void *)android_media_MediaCodec_native_setSurface },
+ { "native_detachOutputSurface",
+ "()V",
+ (void *)android_media_MediaCodec_native_detachOutputSurface },
+
{ "createInputSurface", "()Landroid/view/Surface;",
(void *)android_media_MediaCodec_createInputSurface },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index abb23f516156..c9b6b7f6f337 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -80,6 +80,8 @@ struct JMediaCodec : public AHandler {
status_t setSurface(
const sp<IGraphicBufferProducer> &surface);
+ status_t detachOutputSurface();
+
status_t createInputSurface(sp<IGraphicBufferProducer>* bufferProducer);
status_t setInputSurface(const sp<PersistentSurface> &surface);
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index c48a95625e8f..74b5afe9b65b 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -196,8 +196,7 @@ public final class FadeManagerConfigurationUnitTest {
FadeManagerConfiguration fmcObj = new FadeManagerConfiguration
.Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
- FadeManagerConfiguration fmc = new FadeManagerConfiguration
- .Builder(fmcObj).build();
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(fmcObj).build();
expect.withMessage("Fade state for copy builder").that(fmc.getFadeState())
.isEqualTo(fmcObj.getFadeState());
@@ -249,6 +248,45 @@ public final class FadeManagerConfigurationUnitTest {
}
@Test
+ public void build_withCopyConstructor_doesnotChangeOriginal() {
+ FadeManagerConfiguration copyConstructedFmc = new FadeManagerConfiguration.Builder(mFmc)
+ .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS)
+ .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS)
+ .build();
+
+ expect.withMessage("Fade out duration for media usage of default constructor")
+ .that(mFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of default constructor")
+ .that(mFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of copy constructor")
+ .that(copyConstructedFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of copy constructor")
+ .that(copyConstructedFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_IN_DURATION_MS);
+ }
+
+ @Test
+ public void build_withCopyConstructor_equals() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeableUsages(List.of(AudioAttributes.USAGE_MEDIA,
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ AudioAttributes.USAGE_ASSISTANT,
+ AudioAttributes.USAGE_EMERGENCY))
+ .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS)
+ .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS)
+ .build();
+
+ FadeManagerConfiguration copyConstructedFmc =
+ new FadeManagerConfiguration.Builder(fmc).build();
+
+ expect.withMessage("Fade manager config constructed using copy constructor").that(fmc)
+ .isEqualTo(copyConstructedFmc);
+ }
+
+ @Test
public void testGetDefaultFadeOutDuration() {
expect.withMessage("Default fade out duration")
.that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis())
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 8227bdbd14a7..882afcab6290 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -41,16 +41,15 @@ using namespace aidl::android::os;
using namespace std::chrono_literals;
-using HalSessionHint = aidl::android::hardware::power::SessionHint;
-using HalSessionMode = aidl::android::hardware::power::SessionMode;
-using HalWorkDuration = aidl::android::hardware::power::WorkDuration;
+// Namespace for AIDL types coming from the PowerHAL
+namespace hal = aidl::android::hardware::power;
using android::base::StringPrintf;
struct APerformanceHintSession;
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
-struct AWorkDuration : public HalWorkDuration {};
+struct AWorkDuration : public hal::WorkDuration {};
struct APerformanceHintManager {
public:
@@ -115,7 +114,7 @@ private:
// Last hint reported from sendHint indexed by hint value
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
- std::vector<HalWorkDuration> mActualWorkDurations;
+ std::vector<hal::WorkDuration> mActualWorkDurations;
std::string mSessionName;
static int32_t sIDCounter;
// The most recent set of thread IDs
@@ -207,8 +206,9 @@ APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> h
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
mLastTargetMetTimestamp(0) {
- const std::vector<HalSessionHint> sessionHintRange{ndk::enum_range<HalSessionHint>().begin(),
- ndk::enum_range<HalSessionHint>().end()};
+ const std::vector<hal::SessionHint> sessionHintRange{ndk::enum_range<hal::SessionHint>()
+ .begin(),
+ ndk::enum_range<hal::SessionHint>().end()};
mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
@@ -246,10 +246,10 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano
}
int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
- HalWorkDuration workDuration{.durationNanos = actualDurationNanos,
- .workPeriodStartTimestampNanos = 0,
- .cpuDurationNanos = actualDurationNanos,
- .gpuDurationNanos = 0};
+ hal::WorkDuration workDuration{.durationNanos = actualDurationNanos,
+ .workPeriodStartTimestampNanos = 0,
+ .cpuDurationNanos = actualDurationNanos,
+ .gpuDurationNanos = 0};
return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
}
@@ -323,7 +323,8 @@ int APerformanceHintSession::getThreadIds(int32_t* const threadIds, size_t* size
int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
ndk::ScopedAStatus ret =
- mHintSession->setMode(static_cast<int32_t>(HalSessionMode::POWER_EFFICIENCY), enabled);
+ mHintSession->setMode(static_cast<int32_t>(hal::SessionMode::POWER_EFFICIENCY),
+ enabled);
if (!ret.isOk()) {
ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 9b1330fc048a..6ce83cd7b765 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -367,7 +367,7 @@ void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
- int8_t visibility) {
+ ASurfaceTransactionVisibility visibility) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
@@ -496,7 +496,7 @@ void ASurfaceTransaction_setScale(ASurfaceTransaction* aSurfaceTransaction,
void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
- int8_t transparency) {
+ ASurfaceTransactionTransparency transparency) {
CHECK_NOT_NULL(aSurfaceTransaction);
CHECK_NOT_NULL(aSurfaceControl);
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index a8d8f9a1a55d..8891b50352d1 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.BackgroundThread;
+import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
-import android.utils.BackgroundThread;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -137,17 +137,6 @@ public class PackageWatchdog {
static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
- // Boot loop at which packageWatchdog starts first mitigation
- private static final String BOOT_LOOP_THRESHOLD =
- "persist.device_config.configuration.boot_loop_threshold";
- @VisibleForTesting
- static final int DEFAULT_BOOT_LOOP_THRESHOLD = 15;
- // Once boot_loop_threshold is surpassed next mitigation would be triggered after
- // specified number of reboots.
- private static final String BOOT_LOOP_MITIGATION_INCREMENT =
- "persist.device_config.configuration..boot_loop_mitigation_increment";
- @VisibleForTesting
- static final int DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT = 2;
// Threshold level at which or above user might experience significant disruption.
private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
@@ -253,15 +242,8 @@ public class PackageWatchdog {
mConnectivityModuleConnector = connectivityModuleConnector;
mSystemClock = clock;
mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
- if (Flags.recoverabilityDetection()) {
- mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
- SystemProperties.getInt(BOOT_LOOP_MITIGATION_INCREMENT,
- DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
- } else {
- mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
- }
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
loadFromFile();
sPackageWatchdog = this;
@@ -526,10 +508,16 @@ public class PackageWatchdog {
/**
* Called when the system server boots. If the system server is detected to be in a boot loop,
* query each observer and perform the mitigation action with the lowest user impact.
+ *
+ * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
+ * are not counted in bootloop.
*/
@SuppressWarnings("GuardedBy")
public void noteBoot() {
synchronized (mLock) {
+ // if boot count has reached threshold, start mitigation.
+ // We wait until threshold number of restarts only for the first time. Perform
+ // mitigations for every restart after that.
boolean mitigate = mBootThreshold.incrementAndTest();
if (mitigate) {
if (!Flags.recoverabilityDetection()) {
@@ -557,17 +545,13 @@ public class PackageWatchdog {
}
if (currentObserverToNotify != null) {
if (Flags.recoverabilityDetection()) {
- if (currentObserverImpact < getUserImpactLevelLimit()
- || (currentObserverImpact >= getUserImpactLevelLimit()
- && mBootThreshold.getCount() >= getBootLoopThreshold())) {
- int currentObserverMitigationCount =
- currentObserverInternal.getBootMitigationCount() + 1;
- currentObserverInternal.setBootMitigationCount(
- currentObserverMitigationCount);
- saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.executeBootLoopMitigation(
- currentObserverMitigationCount);
- }
+ int currentObserverMitigationCount =
+ currentObserverInternal.getBootMitigationCount() + 1;
+ currentObserverInternal.setBootMitigationCount(
+ currentObserverMitigationCount);
+ saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
+ currentObserverToNotify.executeBootLoopMitigation(
+ currentObserverMitigationCount);
} else {
mBootThreshold.setMitigationCount(mitigationCount);
mBootThreshold.saveMitigationCountToMetadata();
@@ -647,11 +631,6 @@ public class PackageWatchdog {
DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
}
- private int getBootLoopThreshold() {
- return SystemProperties.getInt(BOOT_LOOP_THRESHOLD,
- DEFAULT_BOOT_LOOP_THRESHOLD);
- }
-
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
@@ -1827,16 +1806,10 @@ public class PackageWatchdog {
private final int mBootTriggerCount;
private final long mTriggerWindow;
- private final int mBootMitigationIncrement;
BootThreshold(int bootTriggerCount, long triggerWindow) {
- this(bootTriggerCount, triggerWindow, /*bootMitigationIncrement=*/ 1);
- }
-
- BootThreshold(int bootTriggerCount, long triggerWindow, int bootMitigationIncrement) {
this.mBootTriggerCount = bootTriggerCount;
this.mTriggerWindow = triggerWindow;
- this.mBootMitigationIncrement = bootMitigationIncrement;
}
public void reset() {
@@ -1915,6 +1888,7 @@ public class PackageWatchdog {
} else {
readMitigationCountFromMetadataIfNecessary();
}
+
final long now = mSystemClock.uptimeMillis();
if (now - getStart() < 0) {
Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
@@ -1939,20 +1913,32 @@ public class PackageWatchdog {
setCount(count);
EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
if (Flags.recoverabilityDetection()) {
- boolean mitigate = (count >= mBootTriggerCount)
- && (count - mBootTriggerCount) % mBootMitigationIncrement == 0;
- return mitigate;
+ // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
+ // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
+ return (count >= mBootTriggerCount)
+ || (performedMitigationsDuringWindow() && count > 1);
}
return count >= mBootTriggerCount;
}
}
@GuardedBy("mLock")
+ private boolean performedMitigationsDuringWindow() {
+ for (ObserverInternal observerInternal: mAllObservers.values()) {
+ if (observerInternal.getBootMitigationCount() > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @GuardedBy("mLock")
private void resetAllObserversBootMitigationCount() {
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
observer.setBootMitigationCount(0);
}
+ saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
}
@GuardedBy("mLock")
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 7093ba42f40d..271d552fc574 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@ import android.content.pm.VersionedPackage;
import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -43,11 +44,10 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import android.utils.ArrayUtils;
-import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -139,7 +139,7 @@ public class RescueParty {
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
@VisibleForTesting
- static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
+ static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
private static final String NAME = "rescue-party-observer";
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
index afcf6895fd0d..a6ae68f62f10 100644
--- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.utils;
+package android.util;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
index fdb15e2333d5..948ebcca0263 100644
--- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.utils;
+package android.util;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
deleted file mode 100644
index fa4d6afc03d3..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
+++ /dev/null
@@ -1,115 +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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
- public static final File[] EMPTY_FILE = new File[0];
-
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(@Nullable T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-
- /** @hide */
- public static @NonNull File[] defeatNullable(@Nullable File[] val) {
- return (val != null) ? val : EMPTY_FILE;
- }
-
- /**
- * Checks if given array is null or has zero elements.
- */
- public static boolean isEmpty(@Nullable int[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * True if the byte array is null or has length 0.
- */
- public static boolean isEmpty(@Nullable byte[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Converts from List of bytes to byte array
- * @param list
- * @return byte[]
- */
- public static byte[] toPrimitive(List<byte[]> list) {
- if (list.size() == 0) {
- return new byte[0];
- }
- int byteLen = list.get(0).length;
- byte[] array = new byte[list.size() * byteLen];
- for (int i = 0; i < list.size(); i++) {
- for (int j = 0; j < list.get(i).length; j++) {
- array[i * byteLen + j] = list.get(i)[j];
- }
- }
- return array;
- }
-
- /**
- * Adds value to given array if not already present, providing set-like
- * behavior.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
- return appendInt(cur, val, false);
- }
-
- /**
- * Adds value to given array.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
- boolean allowDuplicates) {
- if (cur == null) {
- return new int[] { val };
- }
- final int n = cur.length;
- if (!allowDuplicates) {
- for (int i = 0; i < n; i++) {
- if (cur[i] == val) {
- return cur;
- }
- }
- }
- int[] ret = new int[n + 1];
- System.arraycopy(cur, 0, ret, 0, n);
- ret[n] = val;
- return ret;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
deleted file mode 100644
index e4923bfc4ecb..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,128 +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.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
- /**
- * Read a text file into a String, optionally limiting the length.
- *
- * @param file to read (will not seek, so things like /proc files are OK)
- * @param max length (positive for head, negative of tail, 0 for no limit)
- * @param ellipsis to add of the file was truncated (can be null)
- * @return the contents of the file, possibly truncated
- * @throws IOException if something goes wrong reading the file
- * @hide
- */
- public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
- @Nullable String ellipsis) throws IOException {
- InputStream input = new FileInputStream(file);
- // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
- // input stream, bytes read not equal to buffer size is not necessarily the correct
- // indication for EOF; but it is true for BufferedInputStream due to its implementation.
- BufferedInputStream bis = new BufferedInputStream(input);
- try {
- long size = file.length();
- if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
- if (size > 0 && (max == 0 || size < max)) max = (int) size;
- byte[] data = new byte[max + 1];
- int length = bis.read(data);
- if (length <= 0) return "";
- if (length <= max) return new String(data, 0, length);
- if (ellipsis == null) return new String(data, 0, max);
- return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: keep the last N
- int len;
- boolean rolled = false;
- byte[] last = null;
- byte[] data = null;
- do {
- if (last != null) rolled = true;
- byte[] tmp = last;
- last = data;
- data = tmp;
- if (data == null) data = new byte[-max];
- len = bis.read(data);
- } while (len == data.length);
-
- if (last == null && len <= 0) return "";
- if (last == null) return new String(data, 0, len);
- if (len > 0) {
- rolled = true;
- System.arraycopy(last, len, last, 0, last.length - len);
- System.arraycopy(data, 0, last, last.length - len, len);
- }
- if (ellipsis == null || !rolled) return new String(last);
- return ellipsis + new String(last);
- } else { // "cat" mode: size unknown, read it all in streaming fashion
- ByteArrayOutputStream contents = new ByteArrayOutputStream();
- int len;
- byte[] data = new byte[1024];
- do {
- len = bis.read(data);
- if (len > 0) contents.write(data, 0, len);
- } while (len == data.length);
- return contents.toString();
- }
- } finally {
- bis.close();
- input.close();
- }
- }
-
- /**
- * Perform an fsync on the given FileOutputStream. The stream at this
- * point must be flushed but not yet closed.
- *
- * @hide
- */
- public static boolean sync(FileOutputStream stream) {
- try {
- if (stream != null) {
- stream.getFD().sync();
- }
- return true;
- } catch (IOException e) {
- }
- return false;
- }
-
- /**
- * List the files in the directory or return empty file.
- *
- * @hide
- */
- public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
- return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
- : ArrayUtils.EMPTY_FILE;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
deleted file mode 100644
index 5cdc2536129a..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +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.utils;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
- private long[] mValues;
- private int mSize;
- private int mHead;
- private int mTail;
-
- private long[] newUnpaddedLongArray(int num) {
- return new long[num];
- }
- /**
- * Initializes a queue with the given starting capacity.
- *
- * @param initialCapacity the capacity.
- */
- public LongArrayQueue(int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.LONG;
- } else {
- mValues = newUnpaddedLongArray(initialCapacity);
- }
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Initializes a queue with default starting capacity.
- */
- public LongArrayQueue() {
- this(16);
- }
-
- /** @hide */
- public static int growSize(int currentSize) {
- return currentSize <= 4 ? 8 : currentSize * 2;
- }
-
- private void grow() {
- if (mSize < mValues.length) {
- throw new IllegalStateException("Queue not full yet!");
- }
- final int newSize = growSize(mSize);
- final long[] newArray = newUnpaddedLongArray(newSize);
- final int r = mValues.length - mHead; // Number of elements on and to the right of head.
- System.arraycopy(mValues, mHead, newArray, 0, r);
- System.arraycopy(mValues, 0, newArray, r, mHead);
- mValues = newArray;
- mHead = 0;
- mTail = mSize;
- }
-
- /**
- * Returns the number of elements in the queue.
- */
- public int size() {
- return mSize;
- }
-
- /**
- * Removes all elements from this queue.
- */
- public void clear() {
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Adds a value to the tail of the queue.
- *
- * @param value the value to be added.
- */
- public void addLast(long value) {
- if (mSize == mValues.length) {
- grow();
- }
- mValues[mTail] = value;
- mTail = (mTail + 1) % mValues.length;
- mSize++;
- }
-
- /**
- * Removes an element from the head of the queue.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long removeFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final long ret = mValues[mHead];
- mHead = (mHead + 1) % mValues.length;
- mSize--;
- return ret;
- }
-
- /**
- * Returns the element at the given position from the head of the queue, where 0 represents the
- * head of the queue.
- *
- * @param position the position from the head of the queue.
- * @return the element found at the given position.
- * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
- * {@code position} >= {@link #size()}
- */
- public long get(int position) {
- if (position < 0 || position >= mSize) {
- throw new IndexOutOfBoundsException("Index " + position
- + " not valid for a queue of size " + mSize);
- }
- final int index = (mHead + position) % mValues.length;
- return mValues[index];
- }
-
- /**
- * Returns the element at the head of the queue, without removing it.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty
- */
- public long peekFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- return mValues[mHead];
- }
-
- /**
- * Returns the element at the tail of the queue.
- *
- * @return the element at the tail of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long peekLast() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
- return mValues[index];
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- if (mSize <= 0) {
- return "{}";
- }
-
- final StringBuilder buffer = new StringBuilder(mSize * 64);
- buffer.append('{');
- buffer.append(get(0));
- for (int i = 1; i < mSize; i++) {
- buffer.append(", ");
- buffer.append(get(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index dbbef61f6777..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,118 +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.utils;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
- private static final String STRING_ARRAY_SEPARATOR = ":";
-
- /** @hide */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- int type;
- while ((type = parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- // Do nothing
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /** @hide */
- public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
- throws IOException, XmlPullParserException {
- for (;;) {
- int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT
- || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
- return false;
- }
- if (type == XmlPullParser.START_TAG
- && parser.getDepth() == outerDepth + 1) {
- return true;
- }
- }
- }
-
- private static XmlPullParser newPullParser() {
- try {
- XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
- } catch (XmlPullParserException e) {
- throw new AssertionError();
- }
- }
-
- /** @hide */
- public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
- throws IOException {
- final byte[] magic = new byte[4];
- if (in instanceof FileInputStream) {
- try {
- Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
- } else {
- if (!in.markSupported()) {
- in = new BufferedInputStream(in);
- }
- in.mark(8);
- in.read(magic);
- in.reset();
- }
-
- final TypedXmlPullParser xml;
- xml = (TypedXmlPullParser) newPullParser();
- try {
- xml.setInput(in, "UTF_8");
- } catch (XmlPullParserException e) {
- throw new IOException(e);
- }
- return xml;
- }
-}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index bc35a85e48f8..9fd386f38684 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,6 +68,13 @@
<string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
<string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
+ <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
+ <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
<!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
diff --git a/packages/CredentialManager/shared/project.config b/packages/CredentialManager/shared/project.config
new file mode 100644
index 000000000000..f748d6cb9f2e
--- /dev/null
+++ b/packages/CredentialManager/shared/project.config
@@ -0,0 +1,9 @@
+[notify "team"]
+ header = cc
+ email = sgjerry@google.com
+ email = helenqin@google.com
+ email = hemnani@google.com
+ email = shuanghao@google.com
+ email = harinirajan@google.com
+ type = new_changes
+ type = submitted_changes \ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index f2c252ec6422..b408c1553d94 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -51,6 +51,8 @@ import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.EntryInfo
+const val CREDENTIAL_ENTRY_PREFIX = "androidx.credentials.provider.credentialEntry."
+
fun EntryInfo.getIntentSenderRequest(
isAutoSelected: Boolean = false
): IntentSenderRequest? {
@@ -140,7 +142,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -169,7 +172,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -197,7 +201,8 @@ private fun getCredentialOptionInfoList(
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -217,21 +222,26 @@ private fun getCredentialOptionInfoList(
* Note that the required values, such as the provider info's icon or display name, or the entries
* credential type or userName, and finally the display info's app name, are non-null and must
* exist to run through the flow.
+ *
+ * @param hintPrefix a string prefix indicating the type of entry being utilized, since both create
+ * and get flows utilize slice params; includes the final '.' before the name of the type (e.g.
+ * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have
+ * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.")
* // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
* // expected to be used. When it is added, it should be lightly validated.
*/
-private fun predetermineAndValidateBiometricFlow(
- it: Entry
+fun predetermineAndValidateBiometricFlow(
+ entry: Entry,
+ hintPrefix: String,
): BiometricRequestInfo? {
// TODO(b/326243754) : When available, use the official jetpack structured type
- val allowedAuthenticators: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials." +
- "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS")
+ val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
}?.int
// This is optional and does not affect validating the biometric flow in any case
- val opId: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID")
+ val opId: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID")
}?.int
if (allowedAuthenticators != null) {
return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 28c40479962e..a03975375e8a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,6 +18,7 @@ package com.android.credentialmanager
import android.app.Activity
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResult
import android.os.IBinder
import android.text.TextUtils
import android.util.Log
@@ -39,6 +40,7 @@ import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.findBiometricFlowEntry
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.logging.LifecycleEvent
@@ -304,7 +306,11 @@ class CredentialSelectorViewModel(
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState =
- if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+ // An autoselect flow never makes it to the more options screen
+ if (findBiometricFlowEntry(activeEntry = activeEntry,
+ isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION
+ else if (
+ uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
?.contains(activeEntry.activeProvider.id) ?: true ||
!(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
?: false) ||
@@ -330,7 +336,10 @@ class CredentialSelectorViewModel(
)
}
- fun createFlowOnEntrySelected(selectedEntry: EntryInfo) {
+ fun createFlowOnEntrySelected(
+ selectedEntry: EntryInfo,
+ authResult: AuthenticationResult? = null
+ ) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
val entrySubkey = selectedEntry.entrySubkey
@@ -341,6 +350,9 @@ class CredentialSelectorViewModel(
uiState = uiState.copy(
selectedEntry = selectedEntry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
+ biometricState = if (authResult == null) uiState.biometricState else uiState
+ .biometricState.copy(biometricResult = BiometricResult(
+ biometricAuthenticationResult = authResult))
)
} else {
credManRepo.onOptionSelected(
@@ -367,9 +379,4 @@ class CredentialSelectorViewModel(
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
-
- companion object {
- // TODO(b/326243754) : Replace/remove once all failure flows added in
- const val TEMPORARY_FAILURE_CODE = Integer.MIN_VALUE
- }
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index fd6fc6a44c7c..358ebfa1ec90 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -52,10 +52,11 @@ import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
import android.credentials.flags.Flags
+import com.android.credentialmanager.createflow.isBiometricFlow
import com.android.credentialmanager.getflow.TopBrandingContent
+import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow
import java.time.Instant
-
fun getAppLabel(
pm: PackageManager,
appPackageName: String
@@ -237,6 +238,9 @@ class GetFlowUtils {
class CreateFlowUtils {
companion object {
+
+ private const val CREATE_ENTRY_PREFIX = "androidx.credentials.provider.createEntry."
+
/**
* Note: caller required handle empty list due to parsing error.
*/
@@ -417,12 +421,21 @@ class CreateFlowUtils {
}
}
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
+ val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+ compareByDescending { it.first.lastUsedTime }
+ )
+ val activeEntry = toActiveEntry(
+ defaultProvider = defaultProvider,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+ remoteEntry = remoteEntry,
+ remoteEntryProvider = remoteEntryProvider,
+ )
+ val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry,
+ sortedCreateOptionsPairs, requestDisplayInfo)
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
remoteEntry = remoteEntry,
- )
- val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
- compareByDescending { it.first.lastUsedTime }
+ isBiometricFlow = isBiometricFlow
)
return CreateCredentialUiState(
enabledProviders = enabledProviders,
@@ -430,12 +443,7 @@ class CreateFlowUtils {
currentScreenState = initialScreenState,
requestDisplayInfo = requestDisplayInfo,
sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- activeEntry = toActiveEntry(
- defaultProvider = defaultProvider,
- sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- remoteEntry = remoteEntry,
- remoteEntryProvider = remoteEntryProvider,
- ),
+ activeEntry = activeEntry,
remoteEntry = remoteEntry,
foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
)
@@ -444,9 +452,12 @@ class CreateFlowUtils {
fun toCreateScreenState(
createOptionSize: Int,
remoteEntry: RemoteInfo?,
+ isBiometricFlow: Boolean,
): CreateScreenState {
return if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else if (isBiometricFlow) {
+ CreateScreenState.BIOMETRIC_SELECTION
} else {
CreateScreenState.CREATION_OPTION_SELECTION
}
@@ -503,8 +514,8 @@ class CreateFlowUtils {
it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
"SELECT_ALLOWED")
}?.text == "true",
- // TODO(b/326243754) : Handle this when the create flow is added; for now the
- // create flow does not support biometric values
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREATE_ENTRY_PREFIX),
)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index d21077ee7c5a..fa177351be30 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -26,11 +26,14 @@ import androidx.core.content.ContextCompat.getMainExecutor
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.getCreateTitleResCode
import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
+import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.ProviderInfo
import java.lang.Exception
@@ -39,14 +42,30 @@ import java.lang.Exception
* Aggregates common display information used for the Biometric Flow.
* Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
* [providerName], which represents the name of the provider, the [displayTitleText] which is
- * the large text displaying the flow in progress, and the [descriptionAboveBiometricButton], which
+ * the large text displaying the flow in progress, and the [descriptionForCredential], which
* describes details of where the credential is being saved, and how.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ *
+ * 'get' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Use your saved passkey for Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
+ * Your@Email.com"
+ *
+ * 'create' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Create passkey to sign in to Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * ).
+ *
+ * The above are examples; the credential type can change depending on scenario.
+ * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion
*/
data class BiometricDisplayInfo(
val providerIcon: Bitmap,
val providerName: String,
val displayTitleText: String,
- val descriptionAboveBiometricButton: String,
+ val descriptionForCredential: String,
val biometricRequestInfo: BiometricRequestInfo,
)
@@ -56,10 +75,7 @@ data class BiometricDisplayInfo(
* additional states that may improve the flow.
*/
data class BiometricState(
- val biometricResult: BiometricResult? = null,
- val biometricError: BiometricError? = null,
- val biometricHelp: BiometricHelp? = null,
- val biometricAcquireInfo: Int? = null,
+ val biometricResult: BiometricResult? = null
)
/**
@@ -98,7 +114,8 @@ fun runBiometricFlow(
context: Context,
openMoreOptionsPage: () -> Unit,
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
- onCancelFlowAndFinish: (String) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
getRequestDisplayInfo: RequestDisplayInfo? = null,
getProviderInfoList: List<ProviderInfo>? = null,
getProviderDisplayInfo: ProviderDisplayInfo? = null,
@@ -107,18 +124,20 @@ fun runBiometricFlow(
.RequestDisplayInfo? = null,
createProviderInfo: EnabledProviderInfo? = null,
) {
+ // TODO(b/330396089) : Add rotation configuration fix with state machine
var biometricDisplayInfo: BiometricDisplayInfo? = null
+ var flowType = FlowType.GET
if (getRequestDisplayInfo != null) {
biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo,
getProviderInfoList,
getProviderDisplayInfo,
context, biometricEntry)
} else if (createRequestDisplayInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- biometricDisplayInfo = validateBiometricCreateFlow(
+ flowType = FlowType.CREATE
+ biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo,
- createProviderInfo
- )
+ createProviderInfo,
+ context, biometricEntry)
}
if (biometricDisplayInfo == null) {
@@ -127,11 +146,11 @@ fun runBiometricFlow(
}
val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators)
+ biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType)
val callback: BiometricPrompt.AuthenticationCallback =
setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
- onCancelFlowAndFinish)
+ onCancelFlowAndFinish, onIllegalStateAndFinish)
val cancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
@@ -153,23 +172,21 @@ fun runBiometricFlow(
/**
* Sets up the biometric prompt with the UI specific bits.
* // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject
- * // TODO(b/326243754) : Given fallbacks aren't allowed, for now we validate that device creds
- * // are NOT allowed to be passed in to avoid throwing an error. Later, however, once target
- * // alignments occur, we should add the bit back properly.
*/
private fun setupBiometricPrompt(
context: Context,
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
requestAllowedAuthenticators: Int,
+ flowType: FlowType,
): BiometricPrompt {
val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators)
val biometricPrompt = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
// TODO(b/326243754) : Migrate to using new methods recently aligned upon
- .setNegativeButton(context.getString(R.string
- .dropdown_presentation_more_sign_in_options_text),
+ .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string
+ .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
getMainExecutor(context)) { _, _ ->
openMoreOptionsPage()
}
@@ -177,7 +194,7 @@ private fun setupBiometricPrompt(
.setConfirmationRequired(true)
.setLogoBitmap(biometricDisplayInfo.providerIcon)
.setLogoDescription(biometricDisplayInfo.providerName)
- .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton)
+ .setDescription(biometricDisplayInfo.descriptionForCredential)
.build()
return biometricPrompt
@@ -211,7 +228,8 @@ private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
private fun setupBiometricAuthenticationCallback(
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
selectedEntry: EntryInfo,
- onCancelFlowAndFinish: (String) -> Unit
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
): BiometricPrompt.AuthenticationCallback {
val callback: BiometricPrompt.AuthenticationCallback =
object : BiometricPrompt.AuthenticationCallback() {
@@ -224,14 +242,12 @@ private fun setupBiometricAuthenticationCallback(
if (authResult != null) {
sendDataToProvider(selectedEntry, authResult)
} else {
- onCancelFlowAndFinish("The biometric flow succeeded but unexpectedly " +
+ onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
"returned a null value.")
- // TODO(b/326243754) : Propagate to provider
}
} catch (e: Exception) {
- onCancelFlowAndFinish("The biometric flow succeeded but failed on handling " +
+ onIllegalStateAndFinish("The biometric flow succeeded but failed on handling " +
"the result. See: \n$e\n")
- // TODO(b/326243754) : Propagate to provider
}
}
@@ -245,6 +261,12 @@ private fun setupBiometricAuthenticationCallback(
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "Authentication error-ed out: $errorCode and $errString")
+ if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
+ // Note that because the biometric prompt is imbued directly
+ // into the selector, parity applies to the selector's cancellation instead
+ // of the provider's biometric prompt cancellation.
+ onCancelFlowAndFinish()
+ }
// TODO(b/326243754) : Propagate to provider
}
@@ -288,14 +310,16 @@ private fun validateAndRetrieveBiometricGetDisplayInfo(
* checking between the two. The reason for this method matches the logic for the
* [validateBiometricGetFlow] with the only difference being that this is for the create flow.
*/
-private fun validateBiometricCreateFlow(
+private fun validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?,
createProviderInfo: EnabledProviderInfo?,
+ context: Context,
+ selectedEntry: EntryInfo,
): BiometricDisplayInfo? {
if (createRequestDisplayInfo != null && createProviderInfo != null) {
- } else if (createRequestDisplayInfo != null && createProviderInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return createFlowDisplayValues()
+ if (selectedEntry !is CreateOptionInfo) { return null }
+ return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context,
+ selectedEntry)
}
return null
}
@@ -340,17 +364,47 @@ private fun getBiometricDisplayValues(
username
)
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
- displayTitleText = displayTitleText, descriptionAboveBiometricButton = descriptionText,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
- * Handles the biometric sign in via the 'create credentials' flow, or early validates this flow
- * needs to fallback.
+ * Handles the biometric sign in via the create credentials flow. Stricter in the get flow in that
+ * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null
+ * value unlike the get counterpart.
*/
-private fun createFlowDisplayValues(): BiometricDisplayInfo? {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return null
+private fun createBiometricDisplayValues(
+ createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo,
+ createProviderInfo: EnabledProviderInfo,
+ context: Context,
+ selectedEntry: CreateOptionInfo,
+): BiometricDisplayInfo {
+ val icon: Bitmap?
+ val providerName: String?
+ val displayTitleText: String?
+ icon = createProviderInfo.icon.toBitmap()
+ providerName = createProviderInfo.displayName
+ displayTitleText = context.getString(
+ getCreateTitleResCode(createRequestDisplayInfo),
+ createRequestDisplayInfo.appName
+ )
+ val descriptionText: String = context.getString(
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_single_tap_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_single_tap_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_single_tap_sign_in_title
+ },
+ createRequestDisplayInfo.appName,
+ )
+ // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas
+ return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
+ biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
diff --git a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
index dc804ca61dcf..f6140f51b7b5 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,10 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.credentialmanager.common
-/** Constants that vary by compilation configuration. */
-public class Compile {
- /** Whether SystemUI was compiled in debug mode, and supports debug features */
- public static final boolean IS_DEBUG = true;
-}
+enum class FlowType {
+ GET,
+ CREATE
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index e43b09ede1cb..f65a1b750e5e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -20,7 +20,9 @@ import android.credentials.flags.Flags
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -69,6 +71,7 @@ fun ModalBottomSheet(
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
shape = EntryShape.TopRoundedCorner,
+ windowInsets = WindowInsets.navigationBars,
dragHandle = null,
// Never take over the full screen. We always want to leave some top scrim space
// for exiting and viewing the underlying app to help a user gain context.
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index af78573ee9e9..25fb477cbf38 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager.createflow
import android.credentials.flags.Flags.selectorUiImprovementsEnabled
+import android.hardware.biometrics.BiometricPrompt
import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -26,7 +27,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
import androidx.compose.material.icons.Icons
@@ -38,6 +38,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -49,6 +50,7 @@ import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
+import com.android.credentialmanager.common.runBiometricFlow
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.BodyMediumText
import com.android.credentialmanager.common.ui.BodySmallText
@@ -95,6 +97,22 @@ fun CreateCredentialScreen(
viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
onLog = { viewModel.logUiEvent(it) },
)
+ CreateScreenState.BIOMETRIC_SELECTION ->
+ BiometricSelectionPage(
+ biometricEntry = createCredentialUiState
+ .activeEntry?.activeEntryInfo,
+ onCancelFlowAndFinish = viewModel::onUserCancel,
+ onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
+ onMoreOptionSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ onBiometricEntrySelected =
+ viewModel::createFlowOnEntrySelected,
+ fallbackToOriginalFlow =
+ viewModel::getFlowOnBackToPrimarySelectionScreen,
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -313,20 +331,9 @@ fun CreationSelectionCard(
item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
HeadlineText(
- text = when (requestDisplayInfo.type) {
- CredentialType.PASSKEY -> stringResource(
- R.string.choose_create_option_passkey_title,
- requestDisplayInfo.appName
- )
- CredentialType.PASSWORD -> stringResource(
- R.string.choose_create_option_password_title,
- requestDisplayInfo.appName
- )
- CredentialType.UNKNOWN -> stringResource(
- R.string.choose_create_option_sign_in_title,
- requestDisplayInfo.appName
- )
- }
+ text = stringResource(
+ getCreateTitleResCode(requestDisplayInfo),
+ requestDisplayInfo.appName)
)
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
@@ -560,4 +567,32 @@ fun RemoteEntryRow(
iconImageVector = Icons.Outlined.QrCodeScanner,
entryHeadlineText = stringResource(R.string.another_device),
)
-} \ No newline at end of file
+}
+
+@Composable
+internal fun BiometricSelectionPage(
+ biometricEntry: EntryInfo?,
+ onMoreOptionSelected: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderInfo: EnabledProviderInfo,
+ onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalScreenStateAndFinish: (String) -> Unit,
+ fallbackToOriginalFlow: () -> Unit,
+) {
+ if (biometricEntry == null) {
+ fallbackToOriginalFlow()
+ return
+ }
+ runBiometricFlow(
+ biometricEntry = biometricEntry,
+ context = LocalContext.current,
+ openMoreOptionsPage = onMoreOptionSelected,
+ sendDataToProvider = onBiometricEntrySelected,
+ onCancelFlowAndFinish = onCancelFlowAndFinish,
+ createRequestDisplayInfo = requestDisplayInfo,
+ createProviderInfo = enabledProviderInfo,
+ onBiometricFailureFallback = fallbackToOriginalFlow,
+ onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
+ )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 617a981fc4ba..1d262ba5261a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,9 +16,11 @@
package com.android.credentialmanager.createflow
+import android.credentials.flags.Flags.credmanBiometricApiEnabled
import android.graphics.drawable.Drawable
-import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.R
import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.creation.RemoteInfo
@@ -33,14 +35,99 @@ data class CreateCredentialUiState(
val foundCandidateFromUserDefaultProvider: Boolean,
)
+/**
+ * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the
+ * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple
+ * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in
+ * that case, a single 'type' (family only, or work only) for a provider). However, for all other
+ * cases, the biometric screen should always show up if that entry contains the biometric bit.
+ */
+internal fun findBiometricFlowEntry(
+ activeEntry: ActiveEntry,
+ isAutoSelectFlow: Boolean,
+): CreateOptionInfo? {
+ if (!credmanBiometricApiEnabled()) {
+ return null
+ }
+ if (isAutoSelectFlow) {
+ // Since this is the create flow, auto select will only ever be true for a single provider.
+ // However, for all other cases, biometric should be used if that bit is opted into. If
+ // they clash, autoselect is always preferred, but that's only if there's a single provider.
+ return null
+ }
+ val biometricEntry = getCreateEntry(activeEntry)
+ return if (biometricEntry?.biometricRequest != null) biometricEntry else null
+}
+
+/**
+ * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring
+ * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo].
+ */
+internal fun getCreateEntry(
+ activeEntry: ActiveEntry?,
+): CreateOptionInfo? {
+ val entry = activeEntry?.activeEntryInfo
+ if (entry !is CreateOptionInfo) {
+ return null
+ }
+ return entry
+}
+
+/**
+* Determines if the flow is a biometric flow by taking into account autoselect criteria.
+*/
+internal fun isBiometricFlow(
+ activeEntry: ActiveEntry,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+ requestDisplayInfo: RequestDisplayInfo,
+) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable(
+ requestDisplayInfo = requestDisplayInfo,
+ activeEntry = activeEntry,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs
+)) != null
+
+/**
+ * This utility presents the correct resource string for the create flows title conditionally.
+ * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead.
+ * This is for the title, and is a shared resource, unlike the specific unlock request text.
+ * E.g. this will look something like: "Create passkey to sign in to Tribank."
+ * // TODO(b/330396140) : Validate approach and add dynamic auth strings
+ */
+internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int =
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_option_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_option_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_option_sign_in_title
+ }
+
internal fun isFlowAutoSelectable(
uiState: CreateCredentialUiState
): Boolean {
- return uiState.requestDisplayInfo.isAutoSelectRequest &&
- uiState.sortedCreateOptionsPairs.size == 1 &&
- uiState.activeEntry?.activeEntryInfo?.let {
- it is CreateOptionInfo && it.allowAutoSelect
- } ?: false
+ return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry,
+ uiState.sortedCreateOptionsPairs)
+}
+
+/**
+ * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set.
+ * This overloaded method allows identifying if the flow is auto selectable prior to the creation
+ * of the [CreateCredentialUiState].
+ */
+internal fun isFlowAutoSelectable(
+ requestDisplayInfo: RequestDisplayInfo,
+ activeEntry: ActiveEntry?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>
+): Boolean {
+ val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest
+ if (sortedCreateOptionsPairs.size != 1) {
+ return false
+ }
+ val singleEntry = getCreateEntry(activeEntry)
+ return isAutoSelectRequest && singleEntry?.allowAutoSelect == true
}
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -95,6 +182,7 @@ data class ActiveEntry (
/** The name of the current screen. */
enum class CreateScreenState {
CREATION_OPTION_SELECTION,
+ BIOMETRIC_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index b59ccc264630..6d1a3dd98210 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -144,11 +144,10 @@ fun GetCredentialScreen(
} else if (credmanBiometricApiEnabled() && getCredentialUiState
.currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
BiometricSelectionPage(
- // TODO(b/326243754) : Utilize expected entry for this flow, confirm
- // activeEntry will always be what represents the single tap flow
biometricEntry = getCredentialUiState.activeEntry,
onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
- onCancelFlowAndFinish = viewModel::onIllegalUiState,
+ onCancelFlowAndFinish = viewModel::onUserCancel,
+ onIllegalStateAndFinish = viewModel::onIllegalUiState,
requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
providerInfoList = getCredentialUiState.providerInfoList,
providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
@@ -212,7 +211,8 @@ fun GetCredentialScreen(
@Composable
internal fun BiometricSelectionPage(
biometricEntry: EntryInfo?,
- onCancelFlowAndFinish: (String) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
onMoreOptionSelected: () -> Unit,
requestDisplayInfo: RequestDisplayInfo,
providerInfoList: List<ProviderInfo>,
@@ -230,6 +230,7 @@ internal fun BiometricSelectionPage(
openMoreOptionsPage = onMoreOptionSelected,
sendDataToProvider = onBiometricEntrySelected,
onCancelFlowAndFinish = onCancelFlowAndFinish,
+ onIllegalStateAndFinish = onIllegalStateAndFinish,
getRequestDisplayInfo = requestDisplayInfo,
getProviderInfoList = providerInfoList,
getProviderDisplayInfo = providerDisplayInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 6d5b52a7a5f9..ac776af4f627 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -238,6 +238,7 @@ fun toProviderDisplayInfo(
/**
* This generates the res code for the large display title text for the selector. For example, it
* retrieves the resource for strings like: "Use your saved passkey for *rpName*".
+ * TODO(b/330396140) : Validate approach and add dynamic auth strings
*/
internal fun generateDisplayTitleTextResCode(
singleEntryType: CredentialType,
diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp
index 6f4f9ca2e8e1..ec1fe39840f6 100644
--- a/packages/EasterEgg/Android.bp
+++ b/packages/EasterEgg/Android.bp
@@ -83,6 +83,7 @@ java_aconfig_library {
aconfig_declarations {
name: "easter_egg_flags",
package: "com.android.egg.flags",
+ container: "system",
srcs: [
"easter_egg_flags.aconfig",
],
diff --git a/packages/EasterEgg/easter_egg_flags.aconfig b/packages/EasterEgg/easter_egg_flags.aconfig
index 3268a4f5728c..7ddc2389bc92 100644
--- a/packages/EasterEgg/easter_egg_flags.aconfig
+++ b/packages/EasterEgg/easter_egg_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.egg.flags"
+container: "system"
flag {
name: "flag_flag"
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 636f98d8bb95..4f4fb1b02d1a 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -35,85 +35,97 @@ key GRAVE {
}
key 1 {
- label: '1'
+ label: '&'
base: '&'
- shift: '1'
+ shift, capslock: '1'
+ shift+capslock: '&'
}
key 2 {
- label: '2'
+ label: '\u00e9'
base: '\u00e9'
- shift: '2'
+ shift, capslock: '2'
+ shift+capslock: '\u00e9'
ralt: '\u0303'
}
key 3 {
- label: '3'
+ label: '"'
base: '"'
- shift: '3'
+ shift, capslock: '3'
+ shift+capslock: '"'
ralt: '#'
}
key 4 {
- label: '4'
+ label: '\''
base: '\''
- shift: '4'
+ shift, capslock: '4'
+ shift+capslock: '\''
ralt: '{'
}
key 5 {
- label: '5'
+ label: '('
base: '('
- shift: '5'
+ shift, capslock: '5'
+ shift+capslock: '('
ralt: '['
}
key 6 {
- label: '6'
+ label: '-'
base: '-'
- shift: '6'
+ shift, capslock: '6'
+ shift+capslock: '-'
ralt: '|'
}
key 7 {
- label: '7'
+ label: '\u00e8'
base: '\u00e8'
- shift: '7'
+ shift, capslock: '7'
+ shift+capslock: '\u00e8'
ralt: '\u0300'
}
key 8 {
- label: '8'
+ label: '_'
base: '_'
- shift: '8'
+ shift, capslock: '8'
+ shift+capslock: '_'
ralt: '\\'
}
key 9 {
- label: '9'
+ label: '\u00e7'
base: '\u00e7'
- shift: '9'
+ shift, capslock: '9'
+ shift+capslock: '\u00e7'
ralt: '^'
}
key 0 {
- label: '0'
+ label: '\u00e0'
base: '\u00e0'
- shift: '0'
+ shift, capslock: '0'
+ shift+capslock: '\u00e0'
ralt: '@'
}
key MINUS {
label: ')'
base: ')'
- shift: '\u00b0'
+ shift, capslock: '\u00b0'
+ shift+capslock: ')'
ralt: ']'
}
key EQUALS {
label: '='
base: '='
- shift: '+'
+ shift, capslock: '+'
+ shift+capslock: '='
ralt: '}'
}
@@ -193,13 +205,15 @@ key P {
key LEFT_BRACKET {
label: '\u02c6'
base: '\u0302'
- shift: '\u0308'
+ shift, capslock: '\u0308'
+ shift+capslock: '\u0302'
}
key RIGHT_BRACKET {
label: '$'
base: '$'
- shift: '\u00a3'
+ shift, capslock: '\u00a3'
+ shift+capslock: '$'
ralt: '\u00a4'
}
@@ -278,13 +292,15 @@ key M {
key APOSTROPHE {
label: '\u00f9'
base: '\u00f9'
- shift: '%'
+ shift, capslock: '%'
+ shift+capslock: '\u00f9'
}
key BACKSLASH {
label: '*'
base: '*'
- shift: '\u00b5'
+ shift, capslock: '\u00b5'
+ shift+capslock: '*'
}
### ROW 4
@@ -340,23 +356,27 @@ key N {
key COMMA {
label: ','
base: ','
- shift: '?'
+ shift, capslock: '?'
+ shift+capslock: ','
}
key SEMICOLON {
label: ';'
base: ';'
- shift: '.'
+ shift, capslock: '.'
+ shift+capslock: ';'
}
key PERIOD {
label: ':'
base: ':'
- shift: '/'
+ shift, capslock: '/'
+ shift+capslock: ':'
}
key SLASH {
label: '!'
base: '!'
- shift: '\u00a7'
+ shift, capslock: '\u00a7'
+ shift+capslock: '!'
}
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 79c810ca2611..bd84b58aa0f4 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,7 +46,6 @@ android_app {
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -79,7 +78,6 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -112,7 +110,6 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index bf69d3ba7603..05f4d6954a00 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,17 +146,6 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
- <!-- Wearable Components -->
- <service android:name=".wear.WearPackageInstallerService"
- android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
- android:foregroundServiceType="systemExempted"
- android:exported="true"/>
-
- <provider android:name=".wear.WearPackageIconProvider"
- android:authorities="com.google.android.packageinstaller.wear.provider"
- android:grantUriPermissions="true"
- android:exported="false" />
-
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
deleted file mode 100644
index 53a460dc18ca..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Context;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Task that installs an APK. This must not be called on the main thread.
- * This code is based off the Finsky/Wearsky implementation
- */
-public class InstallTask {
- private static final String TAG = "InstallTask";
-
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
- private final Context mContext;
- private String mPackageName;
- private ParcelFileDescriptor mParcelFileDescriptor;
- private PackageInstallerImpl.InstallListener mCallback;
- private PackageInstaller.Session mSession;
- private IntentSender mCommitCallback;
-
- private Exception mException = null;
- private int mErrorCode = 0;
- private String mErrorDesc = null;
-
- public InstallTask(Context context, String packageName,
- ParcelFileDescriptor parcelFileDescriptor,
- PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
- IntentSender commitCallback) {
- mContext = context;
- mPackageName = packageName;
- mParcelFileDescriptor = parcelFileDescriptor;
- mCallback = callback;
- mSession = session;
- mCommitCallback = commitCallback;
- }
-
- public boolean isError() {
- return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
- }
-
- public void execute() {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new IllegalStateException("This method cannot be called from the UI thread.");
- }
-
- OutputStream sessionStream = null;
- try {
- sessionStream = mSession.openWrite(mPackageName, 0, -1);
-
- // 2b: Stream the asset to the installer. Note:
- // Note: writeToOutputStreamFromAsset() always safely closes the input stream
- writeToOutputStreamFromAsset(sessionStream);
- mSession.fsync(sessionStream);
- } catch (Exception e) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
- mErrorDesc = "Could not write to stream";
- } finally {
- if (sessionStream != null) {
- // 2c: close output stream
- try {
- sessionStream.close();
- } catch (Exception e) {
- // Ignore otherwise
- if (mException == null) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
- mErrorDesc = "Could not close session stream";
- }
- }
- }
- }
-
- if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
- // An error occurred, we're done
- Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
- + mErrorDesc + ", " + mException);
- mSession.close();
- mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
- } else {
- // 3. Commit the session (this actually installs it.) Session map
- // will be cleaned up in the callback.
- mCallback.installBeginning();
- mSession.commit(mCommitCallback);
- mSession.close();
- }
- }
-
- /**
- * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
- * corresponding to the {@code Asset} and then write the contents into an
- * {@code OutputStream} that is passed in.
- * <br>
- * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
- */
- private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
- if (outputStream == null) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
- mErrorDesc = "Got a null OutputStream.";
- return false;
- }
-
- if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
- mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
- mErrorDesc = "Could not get FD";
- return false;
- }
-
- InputStream inputStream = null;
- try {
- byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
- int bytesRead;
- inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
-
- while ((bytesRead = inputStream.read(inputBuf)) > -1) {
- if (bytesRead > 0) {
- outputStream.write(inputBuf, 0, bytesRead);
- }
- }
-
- outputStream.flush();
- } catch (IOException e) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
- mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
- return false;
- } finally {
- safeClose(inputStream);
- }
-
- return true;
- }
-
- /**
- * Quietly close a closeable resource (e.g. a stream or file). The input may already
- * be closed and it may even be null.
- */
- public static void safeClose(Closeable resource) {
- if (resource != null) {
- try {
- resource.close();
- } catch (IOException ioe) {
- // Catch and discard the error
- }
- }
- }
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
deleted file mode 100644
index 3daf3d831d97..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-/**
- * Constants for Installation / Uninstallation requests.
- * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
- */
-public class InstallerConstants {
- /** Request succeeded */
- public static final int STATUS_SUCCESS = 0;
-
- /**
- * The new PackageInstaller also returns a small set of less granular error codes, which
- * we'll remap to the range -500 and below to keep away from existing installer codes
- * (which run from -1 to -110).
- */
- public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
-
- public static final int ERROR_COULD_NOT_GET_FD = -603;
- /** This node is not targeted by this request. */
-
- /** The install did not complete because could not create PackageInstaller session */
- public final static int ERROR_INSTALL_CREATE_SESSION = -612;
- /** The install did not complete because could not open PackageInstaller session */
- public final static int ERROR_INSTALL_OPEN_SESSION = -613;
- /** The install did not complete because could not open PackageInstaller output stream */
- public final static int ERROR_INSTALL_OPEN_STREAM = -614;
- /** The install did not complete because of an exception while streaming bytes */
- public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
- /** The install did not complete because of an unexpected exception from PackageInstaller */
- public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
- /** The install did not complete because of an unexpected userActionRequired callback */
- public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
- /** The install did not complete because of an unexpected broadcast (missing fields) */
- public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
- /** The install did not complete because of an error while copying from downloaded file */
- public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
- /** The install did not complete because of an error while copying to the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_COPY_STREAM = -620;
- /** The install did not complete because of an error while closing the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
deleted file mode 100644
index bdc22cf0e276..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Context;
-
-/**
- * Factory that creates a Package Installer.
- */
-public class PackageInstallerFactory {
- private static PackageInstallerImpl sPackageInstaller;
-
- /**
- * Return the PackageInstaller shared object. {@code init} should have already been called.
- */
- public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
- if (sPackageInstaller == null) {
- sPackageInstaller = new PackageInstallerImpl(context);
- }
- return sPackageInstaller;
- }
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
deleted file mode 100644
index 1e37f15f714d..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of package manager installation using modern PackageInstaller api.
- *
- * Heavily copied from Wearsky/Finsky implementation
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class PackageInstallerImpl {
- private static final String TAG = "PackageInstallerImpl";
-
- /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
- private static final String ACTION_INSTALL_COMMIT =
- "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
-
- private final Context mContext;
- private final PackageInstaller mPackageInstaller;
- private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
- private final Map<String, PackageInstaller.Session> mOpenSessionMap;
-
- public PackageInstallerImpl(Context context) {
- mContext = context.getApplicationContext();
- mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
-
- // Capture a map of known sessions
- // This list will be pruned a bit later (stale sessions will be canceled)
- mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
- List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
- for (int i = 0; i < mySessions.size(); i++) {
- PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
- String packageName = sessionInfo.getAppPackageName();
- PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
-
- // Checking for old info is strictly for logging purposes
- if (oldInfo != null) {
- Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
- .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
- }
- }
- mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
- }
-
- /**
- * This callback will be made after an installation attempt succeeds or fails.
- */
- public interface InstallListener {
- /**
- * This callback signals that preflight checks have succeeded and installation
- * is beginning.
- */
- void installBeginning();
-
- /**
- * This callback signals that installation has completed.
- */
- void installSucceeded();
-
- /**
- * This callback signals that installation has failed.
- */
- void installFailed(int errorCode, String errorDesc);
- }
-
- /**
- * This is a placeholder implementation that bundles an entire "session" into a single
- * call. This will be replaced by more granular versions that allow longer session lifetimes,
- * download progress tracking, etc.
- *
- * This must not be called on main thread.
- */
- public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
- final InstallListener callback) {
- // 0. Generic try/catch block because I am not really sure what exceptions (other than
- // IOException) might be thrown by PackageInstaller and I want to handle them
- // at least slightly gracefully.
- try {
- // 1. Create or recover a session, and open it
- // Try recovery first
- PackageInstaller.Session session = null;
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo != null) {
- // See if it's openable, or already held open
- session = getSession(packageName);
- }
- // If open failed, or there was no session, create a new one and open it.
- // If we cannot create or open here, the failure is terminal.
- if (session == null) {
- try {
- innerCreateSession(packageName);
- } catch (IOException ioe) {
- Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
- "Could not create session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- sessionInfo = mSessionInfoMap.get(packageName);
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- mOpenSessionMap.put(packageName, session);
- } catch (SecurityException se) {
- Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
- "Can't open session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- }
-
- // 2. Launch task to handle file operations.
- InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
- callback, session,
- getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
- task.execute();
- if (task.isError()) {
- cancelSession(sessionInfo.getSessionId(), packageName);
- }
- } catch (Exception e) {
- Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
- + e.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
- "Unexpected exception while installing " + packageName);
- }
- }
-
- /**
- * Retrieve an existing session. Will open if needed, but does not attempt to create.
- */
- private PackageInstaller.Session getSession(String packageName) {
- // Check for already-open session
- PackageInstaller.Session session = mOpenSessionMap.get(packageName);
- if (session != null) {
- try {
- // Probe the session to ensure that it's still open. This may or may not
- // throw (if non-open), but it may serve as a canary for stale sessions.
- session.getNames();
- return session;
- } catch (IOException ioe) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
- mOpenSessionMap.remove(packageName);
- } catch (SecurityException se) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
- mOpenSessionMap.remove(packageName);
- }
- }
- // Check to see if this is a known session
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo == null) {
- return null;
- }
- // Try to open it. If we fail here, assume that the SessionInfo was stale.
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- } catch (SecurityException se) {
- Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- } catch (IOException ioe) {
- Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
- + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- }
- mOpenSessionMap.put(packageName, session);
- return session;
- }
-
- /** This version throws an IOException when the session cannot be created */
- private void innerCreateSession(String packageName) throws IOException {
- if (mSessionInfoMap.containsKey(packageName)) {
- Log.w(TAG, "Creating session for " + packageName + " when one already exists");
- return;
- }
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.setAppPackageName(packageName);
-
- // IOException may be thrown at this point
- int sessionId = mPackageInstaller.createSession(params);
- PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
- mSessionInfoMap.put(packageName, sessionInfo);
- }
-
- /**
- * Cancel a session based on its sessionId. Package name is for logging only.
- */
- private void cancelSession(int sessionId, String packageName) {
- // Close if currently held open
- closeSession(packageName);
- // Remove local record
- mSessionInfoMap.remove(packageName);
- try {
- mPackageInstaller.abandonSession(sessionId);
- } catch (SecurityException se) {
- // The session no longer exists, so we can exit quietly.
- return;
- }
- }
-
- /**
- * Close a session if it happens to be held open.
- */
- private void closeSession(String packageName) {
- PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
- if (session != null) {
- // Unfortunately close() is not idempotent. Try our best to make this safe.
- try {
- session.close();
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
- + e.getMessage());
- }
- }
- }
-
- /**
- * Creates a commit callback for the package install that's underway. This will be called
- * some time after calling session.commit() (above).
- */
- private IntentSender getCommitCallback(final String packageName, final int sessionId,
- final InstallListener callback) {
- // Create a single-use broadcast receiver
- BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mContext.unregisterReceiver(this);
- handleCommitCallback(intent, packageName, sessionId, callback);
- }
- };
- // Create a matching intent-filter and register the receiver
- String action = ACTION_INSTALL_COMMIT + "." + packageName;
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(action);
- mContext.registerReceiver(broadcastReceiver, intentFilter,
- Context.RECEIVER_EXPORTED);
-
- // Create a matching PendingIntent and use it to generate the IntentSender
- Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
- broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
- return pendingIntent.getIntentSender();
- }
-
- /**
- * Examine the extras to determine information about the package update/install, decode
- * the result, and call the appropriate callback.
- *
- * @param intent The intent, which the PackageInstaller will have added Extras to
- * @param packageName The package name we created the receiver for
- * @param sessionId The session Id we created the receiver for
- * @param callback The callback to report success/failure to
- */
- private void handleCommitCallback(Intent intent, String packageName, int sessionId,
- InstallListener callback) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installation of " + packageName + " finished with extras "
- + intent.getExtras());
- }
- String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- cancelSession(sessionId, packageName);
- callback.installSucceeded();
- } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
- // TODO - use the constant when the correct/final name is in the SDK
- // TODO This is unexpected, so we are treating as failure for now
- cancelSession(sessionId, packageName);
- callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
- "Unexpected: user action required");
- } else {
- cancelSession(sessionId, packageName);
- int errorCode = getPackageManagerErrorCode(status);
- Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
- + statusMessage);
- callback.installFailed(errorCode, null);
- }
- }
-
- private int getPackageManagerErrorCode(int status) {
- // This is a hack: because PackageInstaller now reports error codes
- // with small positive values, we need to remap them into a space
- // that is more compatible with the existing package manager error codes.
- // See https://sites.google.com/a/google.com/universal-store/documentation
- // /android-client/download-error-codes
- int errorCode;
- if (status == Integer.MIN_VALUE) {
- errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
- } else {
- errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
- }
- return errorCode;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
deleted file mode 100644
index 2c289b2a6f94..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * Installation Util that contains a list of parameters that are needed for
- * installing/uninstalling.
- */
-public class WearPackageArgs {
- private static final String KEY_PACKAGE_NAME =
- "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
- private static final String KEY_ASSET_URI =
- "com.google.android.clockwork.EXTRA_ASSET_URI";
- private static final String KEY_START_ID =
- "com.google.android.clockwork.EXTRA_START_ID";
- private static final String KEY_PERM_URI =
- "com.google.android.clockwork.EXTRA_PERM_URI";
- private static final String KEY_CHECK_PERMS =
- "com.google.android.clockwork.EXTRA_CHECK_PERMS";
- private static final String KEY_SKIP_IF_SAME_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
- private static final String KEY_COMPRESSION_ALG =
- "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
- private static final String KEY_COMPANION_SDK_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
- private static final String KEY_COMPANION_DEVICE_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
- private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
- "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
- private static final String KEY_SKIP_IF_LOWER_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
-
- public static String getPackageName(Bundle b) {
- return b.getString(KEY_PACKAGE_NAME);
- }
-
- public static Bundle setPackageName(Bundle b, String packageName) {
- b.putString(KEY_PACKAGE_NAME, packageName);
- return b;
- }
-
- public static Uri getAssetUri(Bundle b) {
- return b.getParcelable(KEY_ASSET_URI);
- }
-
- public static Uri getPermUri(Bundle b) {
- return b.getParcelable(KEY_PERM_URI);
- }
-
- public static boolean checkPerms(Bundle b) {
- return b.getBoolean(KEY_CHECK_PERMS);
- }
-
- public static boolean skipIfSameVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
- }
-
- public static int getCompanionSdkVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_SDK_VERSION);
- }
-
- public static int getCompanionDeviceVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_DEVICE_VERSION);
- }
-
- public static String getCompressionAlg(Bundle b) {
- return b.getString(KEY_COMPRESSION_ALG);
- }
-
- public static int getStartId(Bundle b) {
- return b.getInt(KEY_START_ID);
- }
-
- public static boolean skipIfLowerVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
- }
-
- public static Bundle setStartId(Bundle b, int startId) {
- b.putInt(KEY_START_ID, startId);
- return b;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
deleted file mode 100644
index 02b9d298db0e..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.List;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-public class WearPackageIconProvider extends ContentProvider {
- private static final String TAG = "WearPackageIconProvider";
- public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
-
- private static final String REQUIRED_PERMISSION =
- "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
-
- /** MIME types. */
- public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException("Query is not supported.");
- }
-
- @Override
- public String getType(Uri uri) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- if (AUTHORITY.equals(uri.getEncodedAuthority())) {
- return ICON_TYPE;
- }
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException("Insert is not supported.");
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- file.delete();
- }
- }
-
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Update is not supported.");
- }
-
- @Override
- public ParcelFileDescriptor openFile(
- Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- }
- }
- return null;
- }
-
- public static Uri getUriForPackage(final String packageName) {
- return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
- }
-
- private String getPackageNameFromUri(Uri uri) {
- if (uri == null) {
- return null;
- }
- List<String> pathSegments = uri.getPathSegments();
- String packageName = pathSegments.get(pathSegments.size() - 1);
-
- if (packageName.endsWith(".icon")) {
- packageName = packageName.substring(0, packageName.lastIndexOf("."));
- }
- return packageName;
- }
-
- /**
- * Make sure the calling app is either a system app or the same app or has the right permission.
- * @throws SecurityException if the caller has insufficient permissions.
- */
- @TargetApi(Build.VERSION_CODES.BASE_1_1)
- private void enforcePermissions(Uri uri) {
- // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
- // allow System process to access this provider.
- Context context = getContext();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final int myUid = android.os.Process.myUid();
-
- if (uid == myUid || isSystemApp(context, pid)) {
- return;
- }
-
- if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
- return;
- }
-
- // last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- == PERMISSION_GRANTED) {
- return;
- }
-
- throw new SecurityException("Permission Denial: reading "
- + getClass().getName() + " uri " + uri + " from pid=" + pid
- + ", uid=" + uid);
- }
-
- /**
- * From the pid of the calling process, figure out whether this is a system app or not. We do
- * this by checking the application information corresponding to the pid and then checking if
- * FLAG_SYSTEM is set.
- */
- @TargetApi(Build.VERSION_CODES.CUPCAKE)
- private boolean isSystemApp(Context context, int pid) {
- // Get the Activity Manager Object
- ActivityManager aManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- // Get the list of running Applications
- List<ActivityManager.RunningAppProcessInfo> rapInfoList =
- aManager.getRunningAppProcesses();
- for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
- if (rapInfo.pid == pid) {
- try {
- PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
- rapInfo.pkgList[0], 0);
- if (pkgInfo != null && pkgInfo.applicationInfo != null &&
- (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- Log.d(TAG, pid + " is a system app.");
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not find package information.", e);
- return false;
- }
- }
- }
- return false;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
deleted file mode 100644
index ae0f4ece1c17..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.Process;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.DeviceUtils;
-import com.android.packageinstaller.PackageUtil;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Service that will install/uninstall packages. It will check for permissions and features as well.
- *
- * -----------
- *
- * Debugging information:
- *
- * Install Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
- * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
- * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
- * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Uninstall Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Retry GMS:
- * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- */
-public class WearPackageInstallerService extends Service
- implements EventResultPersister.EventResultObserver {
- private static final String TAG = "WearPkgInstallerService";
-
- private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
- private final int START_INSTALL = 1;
- private final int START_UNINSTALL = 2;
-
- private int mInstallNotificationId = 1;
- private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
- private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
-
- private class UninstallParams {
- public String mPackageName;
- public PowerManager.WakeLock mLock;
-
- UninstallParams(String packageName, PowerManager.WakeLock lock) {
- mPackageName = packageName;
- mLock = lock;
- }
- }
-
- private final class ServiceHandler extends Handler {
- public ServiceHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case START_INSTALL:
- installPackage(msg.getData());
- break;
- case START_UNINSTALL:
- uninstallPackage(msg.getData());
- break;
- }
- }
- }
- private ServiceHandler mServiceHandler;
- private NotificationChannel mNotificationChannel;
- private static volatile PowerManager.WakeLock lockStatic = null;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- HandlerThread thread = new HandlerThread("PackageInstallerThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
-
- mServiceHandler = new ServiceHandler(thread.getLooper());
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (!DeviceUtils.isWear(this)) {
- Log.w(TAG, "Not running on wearable.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (intent == null) {
- Log.w(TAG, "Got null intent.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Got install/uninstall request " + intent);
- }
-
- Uri packageUri = intent.getData();
- if (packageUri == null) {
- Log.e(TAG, "No package URI in intent");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
- if (packageName == null) {
- Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- if (!lock.isHeld()) {
- lock.acquire();
- }
-
- Bundle intentBundle = intent.getExtras();
- if (intentBundle == null) {
- intentBundle = new Bundle();
- }
- WearPackageArgs.setStartId(intentBundle, startId);
- WearPackageArgs.setPackageName(intentBundle, packageName);
- Message msg;
- String notifTitle;
- if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_INSTALL);
- notifTitle = getString(R.string.installing);
- } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_UNINSTALL);
- notifTitle = getString(R.string.uninstalling);
- } else {
- Log.e(TAG, "Unknown action : " + intent.getAction());
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
- Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
- startForeground(notifPair.first, notifPair.second);
- msg.setData(intentBundle);
- mServiceHandler.sendMessage(msg);
- return START_NOT_STICKY;
- }
-
- private void installPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
- final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
- final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
- boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
- boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
- int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
- int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
- String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
- boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
- ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
- checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
- ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
- companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
- ", skipIfLowerVersion: " + skipIfLowerVersion);
- }
- final PackageManager pm = getPackageManager();
- File tempFile = null;
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- boolean messageSent = false;
- try {
- PackageInfo existingPkgInfo = null;
- try {
- existingPkgInfo = pm.getPackageInfo(packageName,
- PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
- if (existingPkgInfo != null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Replacing package:" + packageName);
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore this exception. We could not find the package, will treat as a new
- // installation.
- }
- // TODO(28021618): This was left as a temp file due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this permission logic might want to be
- // reworked to use a stream instead of a file so that we don't need to write a
- // file at all. Note that there might be some trickiness with opening a stream
- // for multiple users.
- ParcelFileDescriptor parcelFd = getContentResolver()
- .openFileDescriptor(assetUri, "r");
- tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
- parcelFd, packageName, compressionAlg);
- if (tempFile == null) {
- Log.e(TAG, "Could not create a temp file from FD for " + packageName);
- return;
- }
- PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
- PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
- if (pkgInfo == null) {
- Log.e(TAG, "Could not parse apk information for " + packageName);
- return;
- }
-
- if (!pkgInfo.packageName.equals(packageName)) {
- Log.e(TAG, "Wearable Package Name has to match what is provided for " +
- packageName);
- return;
- }
-
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- appInfo.sourceDir = tempFile.getPath();
- appInfo.publicSourceDir = tempFile.getPath();
- getLabelAndUpdateNotification(packageName,
- getString(R.string.installing_app, appInfo.loadLabel(pm)));
-
- List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
-
- // Log if the installed pkg has a higher version number.
- if (existingPkgInfo != null) {
- long longVersionCode = pkgInfo.getLongVersionCode();
- if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
- if (skipIfSameVersion) {
- Log.w(TAG, "Version number (" + longVersionCode +
- ") of new app is equal to existing app for " + packageName +
- "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is equal to existing app for " + packageName);
- }
- } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
- if (skipIfLowerVersion) {
- // Starting in Feldspar, we are not going to allow downgrades of any app.
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() +
- ") for " + packageName + "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
- }
- }
-
- // Following the Android Phone model, we should only check for permissions for any
- // newly defined perms.
- if (existingPkgInfo.requestedPermissions != null) {
- for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
- // If the permission is granted, then we will not ask to request it again.
- if ((existingPkgInfo.requestedPermissionsFlags[i] &
- PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
- " is already granted for " + packageName);
- }
- wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
- }
- }
- }
- }
-
- // Check that the wearable has all the features.
- boolean hasAllFeatures = true;
- for (FeatureInfo feature : pkgInfo.reqFeatures) {
- if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
- (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
- Log.e(TAG, "Wearable does not have required feature: " + feature +
- " for " + packageName);
- hasAllFeatures = false;
- }
- }
-
- if (!hasAllFeatures) {
- return;
- }
-
- // Check permissions on both the new wearable package and also on the already installed
- // wearable package.
- // If the app is targeting API level 23, we will also start a service in ClockworkHome
- // which will ultimately prompt the user to accept/reject permissions.
- if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
- companionDeviceVersion, permUri, wearablePerms, tempFile)) {
- Log.w(TAG, "Wearable does not have enough permissions.");
- return;
- }
-
- // Finally install the package.
- ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
- PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
- new PackageInstallListener(this, lock, startId, packageName));
-
- messageSent = true;
- Log.i(TAG, "Sent installation request for " + packageName);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Could not find the file with URI " + assetUri, e);
- } finally {
- if (!messageSent) {
- // Some error happened. If the message has been sent, we can wait for the observer
- // which will finish the service.
- if (tempFile != null) {
- tempFile.delete();
- }
- finishService(lock, startId);
- }
- }
- }
-
- // TODO: This was left using the old PackageManager API due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this logic should be reworked to use the new
- // PackageInstaller APIs similar to how installPackage was reworked
- private void uninstallPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
-
- UninstallParams params = new UninstallParams(packageName, lock);
- mServiceIdToParams.put(startId, params);
-
- final PackageManager pm = getPackageManager();
- try {
- PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
- getLabelAndUpdateNotification(packageName,
- getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
-
- int uninstallId = UninstallEventReceiver.addObserver(this,
- EventResultPersister.GENERATE_NEW_ID, this);
-
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
- broadcastIntent.setPackage(getPackageName());
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
- broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
-
- // Found package, send uninstall request.
- pm.getPackageInstaller().uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- PackageManager.DELETE_ALL_USERS,
- pendingIntent.getIntentSender());
-
- Log.i(TAG, "Sent delete request for " + packageName);
- } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
- // Couldn't find the package, no need to call uninstall.
- Log.w(TAG, "Could not find package, not deleting " + packageName, e);
- finishService(lock, startId);
- } catch (EventResultPersister.OutOfIdsException e) {
- Log.e(TAG, "Fails to start uninstall", e);
- finishService(lock, startId);
- }
- }
-
- @Override
- public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
- if (mServiceIdToParams.containsKey(serviceId)) {
- UninstallParams params = mServiceIdToParams.get(serviceId);
- try {
- if (status == PackageInstaller.STATUS_SUCCESS) {
- Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
- } else {
- Log.e(TAG, "Package uninstall failed " + params.mPackageName
- + ", returnCode " + legacyStatus);
- }
- } finally {
- finishService(params.mLock, serviceId);
- }
- }
- }
-
- private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
- int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
- File apkFile) {
- // Assumption: We are running on Android O.
- // If the Phone App is targeting M, all permissions may not have been granted to the phone
- // app. If the Wear App is then not targeting M, there may be permissions that are not
- // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
- // app.
- if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
- // Install the app if Wear App is ready for the new perms model.
- return true;
- }
-
- if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
- // All permissions requested by the watch are already granted on the phone, no need
- // to do anything.
- return true;
- }
-
- // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
- if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
- Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
- + "phone app is targeting at least 23, will continue.");
- }
-
- return false;
- }
-
- /**
- * Given a {@string packageName} corresponding to a phone app, query the provider for all the
- * perms that are granted.
- *
- * @return true if the Wear App has any perms that have not been granted yet on the phone side.
- * @return true if there is any error cases.
- */
- private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
- List<String> wearablePermissions) {
- if (permUri == null) {
- Log.e(TAG, "Permission URI is null");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
- Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
- if (permCursor == null) {
- Log.e(TAG, "Could not get the cursor for the permissions");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
-
- Set<String> grantedPerms = new HashSet<>();
- Set<String> ungrantedPerms = new HashSet<>();
- while(permCursor.moveToNext()) {
- // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
- // verify their types.
- if (permCursor.getColumnCount() == 2
- && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
- && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
- String perm = permCursor.getString(0);
- Integer granted = permCursor.getInt(1);
- if (granted == 1) {
- grantedPerms.add(perm);
- } else {
- ungrantedPerms.add(perm);
- }
- }
- }
- permCursor.close();
-
- boolean hasUngrantedPerm = false;
- for (String wearablePerm : wearablePermissions) {
- if (!grantedPerms.contains(wearablePerm)) {
- hasUngrantedPerm = true;
- if (!ungrantedPerms.contains(wearablePerm)) {
- // This is an error condition. This means that the wearable has permissions that
- // are not even declared in its host app. This is a developer error.
- Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
- + "\" that is not defined in the host application's manifest.");
- } else {
- Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
- "\" that is not granted in the host application.");
- }
- }
- }
- return hasUngrantedPerm;
- }
-
- /** Finishes the service after fulfilling obligation to call startForeground. */
- private void finishServiceEarly(int startId) {
- Pair<Integer, Notification> notifPair = buildNotification(
- getApplicationContext().getPackageName(), "");
- startForeground(notifPair.first, notifPair.second);
- finishService(null, startId);
- }
-
- private void finishService(PowerManager.WakeLock lock, int startId) {
- if (lock != null && lock.isHeld()) {
- lock.release();
- }
- stopSelf(startId);
- }
-
- private synchronized PowerManager.WakeLock getLock(Context context) {
- if (lockStatic == null) {
- PowerManager mgr =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- lockStatic = mgr.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
- lockStatic.setReferenceCounted(true);
- }
- return lockStatic;
- }
-
- private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
- private Context mContext;
- private PowerManager.WakeLock mWakeLock;
- private int mStartId;
- private String mApplicationPackageName;
- private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
- int startId, String applicationPackageName) {
- mContext = context;
- mWakeLock = wakeLock;
- mStartId = startId;
- mApplicationPackageName = applicationPackageName;
- }
-
- @Override
- public void installBeginning() {
- Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
- }
-
- @Override
- public void installSucceeded() {
- try {
- Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
-
- // Delete tempFile from the file system.
- File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
- if (tempFile != null) {
- tempFile.delete();
- }
- } finally {
- finishService(mWakeLock, mStartId);
- }
- }
-
- @Override
- public void installFailed(int errorCode, String errorDesc) {
- Log.e(TAG, "Package install failed " + mApplicationPackageName
- + ", errorCode " + errorCode);
- finishService(mWakeLock, mStartId);
- }
- }
-
- private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
- final String title) {
- int notifId;
- if (mNotifIdMap.containsKey(packageName)) {
- notifId = mNotifIdMap.get(packageName);
- } else {
- notifId = mInstallNotificationId++;
- mNotifIdMap.put(packageName, notifId);
- }
-
- if (mNotificationChannel == null) {
- mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
- getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(mNotificationChannel);
- }
- return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
- .setSmallIcon(R.drawable.ic_file_download)
- .setContentTitle(title)
- .build());
- }
-
- private void getLabelAndUpdateNotification(String packageName, String title) {
- // Update notification since we have a label now.
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
- notificationManager.notify(notifPair.first, notifPair.second);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
deleted file mode 100644
index 6a9145db9a06..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.tukaani.xz.LZMAInputStream;
-import org.tukaani.xz.XZInputStream;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class WearPackageUtil {
- private static final String TAG = "WearablePkgInstaller";
-
- private static final String COMPRESSION_LZMA = "lzma";
- private static final String COMPRESSION_XZ = "xz";
-
- public static File getTemporaryFile(Context context, String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "tmp");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- File newFile = new File(newFileDir, packageName + ".apk");
- return newFile;
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- public static File getIconFile(final Context context, final String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "images/icons");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- return new File(newFileDir, packageName + ".icon");
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- /**
- * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
- * by the PackageManager, we will parse it before sending it to the PackageManager.
- * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
- * the fd to a File.
- *
- * @param context
- * @param fd FileDescriptor to convert to File
- * @param packageName Name of package, will define the name of the file
- * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
- * decompress it here
- */
- public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
- String packageName, String compressionAlg) {
- File newFile = getTemporaryFile(context, packageName);
- if (fd == null || fd.getFileDescriptor() == null) {
- return null;
- }
- InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- try {
- if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
- fr = new XZInputStream(fr);
- } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
- fr = new LZMAInputStream(fr);
- }
- } catch (IOException e) {
- Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
- return null;
- }
-
- int nRead;
- byte[] data = new byte[1024];
- try {
- final FileOutputStream fo = new FileOutputStream(newFile);
- while ((nRead = fr.read(data, 0, data.length)) != -1) {
- fo.write(data, 0, nRead);
- }
- fo.flush();
- fo.close();
- Os.chmod(newFile.getAbsolutePath(), 0644);
- return newFile;
- } catch (IOException e) {
- Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
- return null;
- } catch (ErrnoException e) {
- Log.e(TAG, "Could not set permissions on file ", e);
- return null;
- } finally {
- try {
- fr.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close the file from FD ", e);
- }
- }
- }
-
- /**
- * @return com.google.com from expected formats like
- * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
- */
- public static String getSanitizedPackageName(Uri packageUri) {
- String packageName = packageUri.getEncodedSchemeSpecificPart();
- if (packageName != null) {
- return packageName.replaceAll("^/+", "");
- }
- return packageName;
- }
-}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 66fad3689eac..d6cbf2a0f581 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -22,7 +22,7 @@ android_library {
"guava",
"WifiTrackerLibRes",
- "iconloader",
+ "//frameworks/libs/systemui:iconloader",
"setupdesign",
"SettingsLibActionBarShadow",
@@ -88,6 +88,7 @@ java_defaults {
aconfig_declarations {
name: "settingslib_media_flags",
package: "com.android.settingslib.media.flags",
+ container: "system",
srcs: [
"aconfig/settingslib_media_flag_declarations.aconfig",
],
@@ -101,6 +102,7 @@ java_aconfig_library {
aconfig_declarations {
name: "settingslib_flags",
package: "com.android.settingslib.flags",
+ container: "system",
srcs: [
"aconfig/settingslib.aconfig",
],
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
new file mode 100644
index 000000000000..01dfd7d71964
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
+ <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
+ <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
+ <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
+
+ <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
+ <!-- component module background -->
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
+ <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
+ <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
+ <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index b55dd1bc2439..a6d8ca4660cd 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -23,7 +23,10 @@
<item name="android:windowNoTitle">true</item>
</style>
- <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
+ <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
<style name="Theme.SpaLib.BottomSheetDialog" parent="Theme.SpaLib">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaDialogWindowTypeActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaDialogWindowTypeActivity.kt
new file mode 100644
index 000000000000..46975f51adeb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaDialogWindowTypeActivity.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa
+
+import android.content.Context
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.appcompat.app.AlertDialog
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ComposeView
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+/**
+ * Dialog activity when the dialog window type need to be override.
+ *
+ * Please use [SpaBaseDialogActivity] for all other use cases.
+ */
+abstract class SpaDialogWindowTypeActivity : ComponentActivity() {
+ private val spaEnvironment get() = SpaEnvironmentFactory.instance
+ private var dialog: AlertDialogWithType? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+
+ dialog = AlertDialogWithType(this).apply { show() }
+ }
+
+ override fun finish() {
+ dialog?.dismiss()
+ super.finish()
+ }
+
+ abstract val dialogWindowType: Int?
+
+ @Composable
+ abstract fun Content()
+
+ inner class AlertDialogWithType(context: Context) :
+ AlertDialog(context, R.style.Theme_SpaLib_Dialog) {
+
+ init {
+ setView(ComposeView(context).apply {
+ setContent {
+ SettingsTheme {
+ this@SpaDialogWindowTypeActivity.Content()
+ }
+ }
+ })
+ setOnDismissListener { finish() }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ dialogWindowType?.let { window?.setType(it) }
+ super.onCreate(savedInstanceState)
+ }
+ }
+
+ companion object {
+ private const val TAG = "SpaBaseDialogActivity"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
new file mode 100644
index 000000000000..bef0bca1c5a4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialogDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import kotlin.math.max
+
+@Composable
+fun SettingsAlertDialogContent(
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ icon: @Composable (() -> Unit)? = {
+ Icon(
+ Icons.Default.WarningAmber,
+ contentDescription = null
+ )
+ },
+ text: @Composable (() -> Unit)?,
+) {
+ SettingsAlertDialogContent(
+ buttons = {
+ AlertDialogFlowRow(
+ mainAxisSpacing = ButtonsMainAxisSpacing,
+ crossAxisSpacing = ButtonsCrossAxisSpacing
+ ) {
+ dismissButton?.let {
+ OutlinedButton(onClick = it.onClick) {
+ Text(it.text)
+ }
+ }
+ confirmButton?.let {
+ Button(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ }
+ },
+ icon = icon,
+ modifier = Modifier.width(getDialogWidth()),
+ title = title?.let {
+ {
+ Text(
+ it,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ )
+}
+
+@Composable
+internal fun SettingsAlertDialogContent(
+ buttons: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: (@Composable () -> Unit)?,
+ title: (@Composable () -> Unit)?,
+ text: @Composable (() -> Unit)?,
+) {
+ Surface(
+ modifier = modifier,
+ shape = SettingsShape.CornerExtraLarge,
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ ) {
+ Column(
+ modifier = Modifier.padding(DialogPadding)
+ ) {
+ icon?.let {
+ CompositionLocalProvider(
+ LocalContentColor provides AlertDialogDefaults.iconContentColor,
+ ) {
+ Box(
+ Modifier
+ .padding(IconPadding)
+ .align(Alignment.CenterHorizontally)
+ ) {
+ icon()
+ }
+ }
+ }
+ title?.let {
+ ProvideContentColorTextStyle(
+ contentColor = AlertDialogDefaults.titleContentColor,
+ textStyle = MaterialTheme.typography.headlineSmall,
+ ) {
+ Box(
+ // Align the title to the center when an icon is present.
+ Modifier
+ .padding(TitlePadding)
+ .align(
+ if (icon == null) {
+ Alignment.Start
+ } else {
+ Alignment.CenterHorizontally
+ }
+ )
+ ) {
+ title()
+ }
+ }
+ }
+ text?.let {
+ ProvideContentColorTextStyle(
+ contentColor = AlertDialogDefaults.textContentColor,
+ textStyle = MaterialTheme.typography.bodyMedium
+ ) {
+ Box(
+ Modifier
+ .weight(weight = 1f, fill = false)
+ .padding(TextPadding)
+ .align(Alignment.Start)
+ ) {
+ text()
+ }
+ }
+ }
+ Box(modifier = Modifier.align(Alignment.End)) {
+ ProvideContentColorTextStyle(
+ contentColor = MaterialTheme.colorScheme.primary,
+ textStyle = MaterialTheme.typography.labelLarge,
+ content = buttons
+ )
+ }
+ }
+ }
+}
+
+@Composable
+internal fun AlertDialogFlowRow(
+ mainAxisSpacing: Dp,
+ crossAxisSpacing: Dp,
+ content: @Composable () -> Unit
+) {
+ Layout(content) { measurables, constraints ->
+ val sequences = mutableListOf<List<Placeable>>()
+ val crossAxisSizes = mutableListOf<Int>()
+ val crossAxisPositions = mutableListOf<Int>()
+
+ var mainAxisSpace = 0
+ var crossAxisSpace = 0
+
+ val currentSequence = mutableListOf<Placeable>()
+ var currentMainAxisSize = 0
+ var currentCrossAxisSize = 0
+
+ // Return whether the placeable can be added to the current sequence.
+ fun canAddToCurrentSequence(placeable: Placeable) =
+ currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
+ placeable.width <= constraints.maxWidth
+
+ // Store current sequence information and start a new sequence.
+ fun startNewSequence() {
+ if (sequences.isNotEmpty()) {
+ crossAxisSpace += crossAxisSpacing.roundToPx()
+ }
+ // Ensures that confirming actions appear above dismissive actions.
+ @Suppress("ListIterator")
+ sequences.add(0, currentSequence.toList())
+ crossAxisSizes += currentCrossAxisSize
+ crossAxisPositions += crossAxisSpace
+
+ crossAxisSpace += currentCrossAxisSize
+ mainAxisSpace = max(mainAxisSpace, currentMainAxisSize)
+
+ currentSequence.clear()
+ currentMainAxisSize = 0
+ currentCrossAxisSize = 0
+ }
+
+ measurables.fastForEach { measurable ->
+ // Ask the child for its preferred size.
+ val placeable = measurable.measure(constraints)
+
+ // Start a new sequence if there is not enough space.
+ if (!canAddToCurrentSequence(placeable)) startNewSequence()
+
+ // Add the child to the current sequence.
+ if (currentSequence.isNotEmpty()) {
+ currentMainAxisSize += mainAxisSpacing.roundToPx()
+ }
+ currentSequence.add(placeable)
+ currentMainAxisSize += placeable.width
+ currentCrossAxisSize = max(currentCrossAxisSize, placeable.height)
+ }
+
+ if (currentSequence.isNotEmpty()) startNewSequence()
+
+ val mainAxisLayoutSize = max(mainAxisSpace, constraints.minWidth)
+
+ val crossAxisLayoutSize = max(crossAxisSpace, constraints.minHeight)
+
+ val layoutWidth = mainAxisLayoutSize
+
+ val layoutHeight = crossAxisLayoutSize
+
+ layout(layoutWidth, layoutHeight) {
+ sequences.fastForEachIndexed { i, placeables ->
+ val childrenMainAxisSizes = IntArray(placeables.size) { j ->
+ placeables[j].width +
+ if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
+ }
+ val arrangement = Arrangement.End
+ val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
+ with(arrangement) {
+ arrange(
+ mainAxisLayoutSize, childrenMainAxisSizes,
+ layoutDirection, mainAxisPositions
+ )
+ }
+ placeables.fastForEachIndexed { j, placeable ->
+ placeable.place(
+ x = mainAxisPositions[j],
+ y = crossAxisPositions[i]
+ )
+ }
+ }
+ }
+ }
+}
+
+// Paddings for each of the dialog's parts.
+private val DialogPadding = PaddingValues(all = 24.dp)
+private val IconPadding = PaddingValues(bottom = 16.dp)
+private val TitlePadding = PaddingValues(bottom = 16.dp)
+private val TextPadding = PaddingValues(bottom = 24.dp)
+
+private val ButtonsMainAxisSpacing = 8.dp
+private val ButtonsCrossAxisSpacing = 12.dp
+
+/**
+ * ProvideContentColorTextStyle
+ *
+ * A convenience method to provide values to both LocalContentColor and LocalTextStyle in
+ * one call. This is less expensive than nesting calls to CompositionLocalProvider.
+ *
+ * Text styles will be merged with the current value of LocalTextStyle.
+ */
+@Composable
+private fun ProvideContentColorTextStyle(
+ contentColor: Color,
+ textStyle: TextStyle,
+ content: @Composable () -> Unit
+) {
+ val mergedStyle = LocalTextStyle.current.merge(textStyle)
+ CompositionLocalProvider(
+ LocalContentColor provides contentColor,
+ LocalTextStyle provides mergedStyle,
+ content = content
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 8b546b4de069..791893b3c056 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -23,7 +23,6 @@ import com.android.settingslib.spa.widget.ui.SettingsSwitch
@Composable
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
@@ -33,7 +32,7 @@ fun TwoTargetSwitchPreference(
summary = model.summary,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
- icon = icon,
+ icon = model.icon,
) {
SettingsSwitch(
checked = model.checked(),
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index 5c2d7701fd6f..1f7122e82c30 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -33,11 +33,13 @@ fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem(
model = object : SwitchPreferenceModel {
override val title = label
override val summary = this@AppListTwoTargetSwitchItem.summary
+ override val icon = @Composable {
+ AppIcon(record.app, SettingsDimension.appIconItemSize)
+ }
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
},
- icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
primaryOnClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index 87cd2b844a2b..c9934adfad22 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -52,6 +52,8 @@ internal class RestrictedSwitchPreferenceModel(
checked = model.checked,
)
+ override val icon = model.icon
+
override val checked = when (restrictedMode) {
null -> ({ null })
is NoRestricted -> model.checked
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
index e100773b2358..1bed73365e80 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -29,14 +29,12 @@ import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitc
@Composable
fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
restrictions: Restrictions,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
RestrictedTwoTargetSwitchPreference(
model = model,
- icon = icon,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
restrictions = restrictions,
@@ -48,21 +46,19 @@ fun RestrictedTwoTargetSwitchPreference(
@Composable
internal fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
restrictions: Restrictions,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.isEmpty()) {
- TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+ TwoTargetSwitchPreference(model, primaryEnabled, primaryOnClick)
return
}
val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
TwoTargetSwitchPreference(
model = restrictedModel,
- icon = icon,
primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
)
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 54c5a14702f6..e09ab0086451 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -1,4 +1,5 @@
package: "com.android.settingslib.flags"
+container: "system"
flag {
name: "new_status_bar_icons"
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index f3e537b33230..4d70aec9fa5c 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -1,4 +1,5 @@
package: "com.android.settingslib.media.flags"
+container: "system"
flag {
name: "use_media_router2_for_info_media_manager"
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index ad0e6f46d9c8..e95a506376fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -67,7 +67,6 @@ import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.settingslib.utils.BuildCompatUtils;
-import java.time.Duration;
import java.util.List;
public class Utils {
@@ -76,21 +75,10 @@ public class Utils {
public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
"incompatible_charger_warning_disabled";
- public static final String WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP =
- "wireless_charging_notification_timestamp";
@VisibleForTesting
static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
- @VisibleForTesting static final long WIRELESS_CHARGING_DEFAULT_TIMESTAMP = -1L;
-
- @VisibleForTesting
- static final long WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS =
- Duration.ofDays(30).toMillis();
-
- @VisibleForTesting
- static final String WIRELESS_CHARGING_WARNING_ENABLED = "wireless_charging_warning_enabled";
-
private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
private static String sServicesSystemSharedLibPackageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index 6b833cc68b93..0282f03e0b98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import android.annotation.NonNull;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -25,6 +26,7 @@ import android.os.UserHandle;
import androidx.annotation.VisibleForTesting;
import java.io.IOException;
+import java.util.UUID;
/**
* StorageStatsSource wraps the StorageStatsManager for testability purposes.
@@ -59,6 +61,10 @@ public class StorageStatsSource {
return mStorageStatsManager.getCacheQuotaBytes(volumeUuid, uid);
}
+ public long getTotalBytes(@NonNull UUID storageUuid) throws IOException {
+ return mStorageStatsManager.getTotalBytes(storageUuid);
+ }
+
/**
* Static class that provides methods for querying the amount of external storage available as
* well as breaking it up into several media types.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 57fcc7462a65..a906875e11ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -3,10 +3,13 @@ package com.android.settingslib.bluetooth;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -30,6 +33,7 @@ import androidx.annotation.WorkerThread;
import androidx.core.graphics.drawable.IconCompat;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
@@ -46,14 +50,14 @@ public class BluetoothUtils {
private static final String TAG = "BluetoothUtils";
public static final boolean V = false; // verbose logging
- public static final boolean D = true; // regular logging
+ public static final boolean D = true; // regular logging
public static final int META_INT_ERROR = -1;
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
- private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
- "com.google.android.gms.dck");
+ private static final Set<String> EXCLUSIVE_MANAGERS =
+ ImmutableSet.of("com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -89,23 +93,23 @@ public class BluetoothUtils {
/**
* @param context to access resources from
* @param cachedDevice to get class from
- * @return pair containing the drawable and the description of the Bluetooth class
- * of the device.
+ * @return pair containing the drawable and the description of the Bluetooth class of the
+ * device.
*/
- public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
BluetoothClass btClass = cachedDevice.getBtClass();
if (btClass != null) {
switch (btClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_laptop),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_laptop),
context.getString(R.string.bluetooth_talkback_computer));
case BluetoothClass.Device.Major.PHONE:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_phone),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone),
context.getString(R.string.bluetooth_talkback_phone));
case BluetoothClass.Device.Major.PERIPHERAL:
@@ -115,8 +119,8 @@ public class BluetoothUtils {
case BluetoothClass.Device.Major.IMAGING:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_print),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_settings_print),
context.getString(R.string.bluetooth_talkback_imaging));
default:
@@ -125,8 +129,9 @@ public class BluetoothUtils {
}
if (cachedDevice.isHearingAidDevice()) {
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_hearing_aid),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_hearing_aid),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
@@ -138,7 +143,8 @@ public class BluetoothUtils {
// The device should show hearing aid icon if it contains any hearing aid related
// profiles
if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
- return new Pair<>(getBluetoothDrawable(context, profileResId),
+ return new Pair<>(
+ getBluetoothDrawable(context, profileResId),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
if (resId == 0) {
@@ -153,42 +159,40 @@ public class BluetoothUtils {
if (btClass != null) {
if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headset_hfp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headset_hfp),
context.getString(R.string.bluetooth_talkback_headset));
}
if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headphones_a2dp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headphones_a2dp),
context.getString(R.string.bluetooth_talkback_headphone));
}
}
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth)
+ .mutate(),
context.getString(R.string.bluetooth_talkback_bluetooth));
}
- /**
- * Get bluetooth drawable by {@code resId}
- */
+ /** Get bluetooth drawable by {@code resId} */
public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) {
return context.getDrawable(resId);
}
- /**
- * Get colorful bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ /** Get colorful bluetooth icon with description */
+ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
final Resources resources = context.getResources();
- final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
- cachedDevice);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice);
if (pair.first instanceof BitmapDrawable) {
- return new Pair<>(new AdaptiveOutlineDrawable(
- resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+ return new Pair<>(
+ new AdaptiveOutlineDrawable(
+ resources, ((BitmapDrawable) pair.first).getBitmap()),
+ pair.second);
}
int hashCode;
@@ -198,15 +202,12 @@ public class BluetoothUtils {
hashCode = cachedDevice.getAddress().hashCode();
}
- return new Pair<>(buildBtRainbowDrawable(context,
- pair.first, hashCode), pair.second);
+ return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second);
}
- /**
- * Build Bluetooth device icon with rainbow
- */
- private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
- int hashCode) {
+ /** Build Bluetooth device icon with rainbow */
+ private static Drawable buildBtRainbowDrawable(
+ Context context, Drawable drawable, int hashCode) {
final Resources resources = context.getResources();
// Deal with normal headset
@@ -222,38 +223,37 @@ public class BluetoothUtils {
return adaptiveIcon;
}
- /**
- * Get bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- context, cachedDevice);
+ /** Get bluetooth icon with description */
+ public static Pair<Drawable, String> getBtDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.bt_nearby_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size);
final Resources resources = context.getResources();
// Deal with advanced device icon
if (isAdvancedDetailsHeader(bluetoothDevice)) {
- final Uri iconUri = getUriMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON);
+ final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON);
if (iconUri != null) {
try {
- context.getContentResolver().takePersistableUriPermission(iconUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ context.getContentResolver()
+ .takePersistableUriPermission(
+ iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
}
try {
- final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
- context.getContentResolver(), iconUri);
+ final Bitmap bitmap =
+ MediaStore.Images.Media.getBitmap(
+ context.getContentResolver(), iconUri);
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
- return new Pair<>(new BitmapDrawable(resources,
- resizedBitmap), pair.second);
+ return new Pair<>(
+ new BitmapDrawable(resources, resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
@@ -280,8 +280,8 @@ public class BluetoothUtils {
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)
@@ -306,8 +306,8 @@ public class BluetoothUtils {
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
return true;
@@ -321,15 +321,15 @@ public class BluetoothUtils {
* @param device Must be one of the public constants in {@link BluetoothClass.Device}
* @return true if device class matches, false otherwise.
*/
- public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
- int device) {
+ public static boolean isDeviceClassMatched(
+ @NonNull BluetoothDevice bluetoothDevice, int device) {
final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass();
return bluetoothClass != null && bluetoothClass.getDeviceClass() == device;
}
private static boolean isAdvancedHeaderEnabled() {
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
- true)) {
+ if (!DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) {
Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
return false;
}
@@ -345,9 +345,7 @@ public class BluetoothUtils {
return false;
}
- /**
- * Create an Icon pointing to a drawable.
- */
+ /** Create an Icon pointing to a drawable. */
public static IconCompat createIconWithDrawable(Drawable drawable) {
Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
@@ -355,19 +353,15 @@ public class BluetoothUtils {
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
return IconCompat.createWithBitmap(bitmap);
}
- /**
- * Build device icon with advanced outline
- */
+ /** Build device icon with advanced outline */
public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.advanced_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size);
final Resources resources = context.getResources();
Bitmap bitmap = null;
@@ -376,14 +370,12 @@ public class BluetoothUtils {
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED);
}
@@ -391,9 +383,7 @@ public class BluetoothUtils {
return drawable;
}
- /**
- * Creates a drawable with specified width and height.
- */
+ /** Creates a drawable with specified width and height. */
public static Bitmap createBitmap(Drawable drawable, int width, int height) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
@@ -487,11 +477,8 @@ public class BluetoothUtils {
}
/**
- * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
- * 1) currently connected
- * 2) is Hearing Aid or LE Audio
- * OR
- * 3) connected profile matches currentAudioProfile
+ * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently
+ * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -519,8 +506,11 @@ public class BluetoothUtils {
// It would show in Available Devices group.
if (cachedDevice.isConnectedAshaHearingAidDevice()
|| cachedDevice.isConnectedLeAudioDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
// According to the current audio profile type,
@@ -541,11 +531,79 @@ public class BluetoothUtils {
return isFilterMatched;
}
+ /** Returns if the le audio sharing is enabled. */
+ public static boolean isAudioSharingEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return Flags.enableLeAudioSharing()
+ && adapter.isLeAudioBroadcastSourceSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED
+ && adapter.isLeAudioBroadcastAssistantSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED;
+ }
+
+ /** Returns if the broadcast is on-going. */
+ @WorkerThread
+ public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
+ if (manager == null) return false;
+ LocalBluetoothLeBroadcast broadcast =
+ manager.getProfileManager().getLeAudioBroadcastProfile();
+ return broadcast != null && broadcast.isEnabled(null);
+ }
+
+ /**
+ * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ *
+ * @param cachedDevice The cached bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has connected to a broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasConnectedBroadcastSource(
+ CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
+ if (localBtManager == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
+ return false;
+ }
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ assistant.getAllSources(cachedDevice.getDevice());
+ if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Lead device has connected broadcast source, device = "
+ + cachedDevice.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ // Return true if member device is in broadcast.
+ for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
+ List<BluetoothLeBroadcastReceiveState> list =
+ assistant.getAllSources(device.getDevice());
+ if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Member device has connected broadcast source, device = "
+ + device.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Checks the connectivity status based on the provided broadcast receive state. */
+ @WorkerThread
+ public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+ return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+ }
+
/**
- * Checks if the Bluetooth device is an available hearing device, which means:
- * 1) currently connected
- * 2) is Hearing Aid
- * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP)
+ * Checks if the Bluetooth device is an available hearing device, which means: 1) currently
+ * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
+ * ASHA, HAP)
*
* @param cachedDevice the CachedBluetoothDevice
* @return if the device is Available hearing device
@@ -553,19 +611,20 @@ public class BluetoothUtils {
@WorkerThread
public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) {
if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
return false;
}
/**
- * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
- * 1) currently connected
- * 2) is not Hearing Aid or LE Audio
- * AND
- * 3) connected profile does not match currentAudioProfile
+ * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently
+ * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match
+ * currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -675,29 +734,28 @@ public class BluetoothUtils {
}
/**
- * Returns the BluetoothDevice's exclusive manager
- * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
- * given set, otherwise null.
+ * Returns the BluetoothDevice's exclusive manager ({@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
+ * set, otherwise null.
*/
@Nullable
private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
- byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+ byte[] exclusiveManagerNameBytes =
+ bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
if (exclusiveManagerNameBytes == null) {
- Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
- + " doesn't have exclusive manager");
+ Log.d(
+ TAG,
+ "Bluetooth device "
+ + bluetoothDevice.getName()
+ + " doesn't have exclusive manager");
return null;
}
String exclusiveManagerName = new String(exclusiveManagerNameBytes);
- return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
- : null;
+ return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
}
- /**
- * Checks if given package is installed
- */
- private static boolean isPackageInstalled(Context context,
- String packageName) {
+ /** Checks if given package is installed */
+ private static boolean isPackageInstalled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getPackageInfo(packageName, 0);
@@ -709,13 +767,12 @@ public class BluetoothUtils {
}
/**
- * A BluetoothDevice is exclusively managed if
- * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
- * 2) the exclusive manager app name is in the allowlist.
- * 3) the exclusive manager app is installed.
+ * A BluetoothDevice is exclusively managed if 1) it has field {@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
+ * in the allowlist. 3) the exclusive manager app is installed.
*/
- public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
- @NonNull BluetoothDevice bluetoothDevice) {
+ public static boolean isExclusivelyManagedBluetoothDevice(
+ @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
if (exclusiveManagerName == null) {
return false;
@@ -728,9 +785,7 @@ public class BluetoothUtils {
}
}
- /**
- * Return the allowlist for exclusive manager names.
- */
+ /** Return the allowlist for exclusive manager names. */
@NonNull
public static Set<String> getExclusiveManagers() {
return EXCLUSIVE_MANAGERS;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e34c50eb5ce6..9b1e4b7a633b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -174,6 +174,7 @@ public abstract class InfoMediaManager {
public void startScan() {
mMediaDevices.clear();
+ registerRouter();
startScanOnRouter();
updateRouteListingPreference();
refreshDevices();
@@ -188,10 +189,19 @@ public abstract class InfoMediaManager {
}
}
- public abstract void stopScan();
+ public final void stopScan() {
+ stopScanOnRouter();
+ unregisterRouter();
+ }
+
+ protected abstract void stopScanOnRouter();
protected abstract void startScanOnRouter();
+ protected abstract void registerRouter();
+
+ protected abstract void unregisterRouter();
+
protected abstract void transferToRoute(@NonNull MediaRoute2Info route);
protected abstract void selectRoute(
@@ -244,8 +254,6 @@ public abstract class InfoMediaManager {
protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName);
protected final void rebuildDeviceList() {
- mMediaDevices.clear();
- mCurrentConnectedDevice = null;
buildAvailableRoutes();
}
@@ -514,17 +522,27 @@ public abstract class InfoMediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
- for (MediaRoute2Info route : getAvailableRoutes()) {
+ mMediaDevices.clear();
+ RoutingSessionInfo activeSession = getActiveRoutingSession();
+
+ for (MediaRoute2Info route : getAvailableRoutes(activeSession)) {
if (DEBUG) {
Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ route.getVolume() + ", type : " + route.getType());
}
- addMediaDevice(route);
+ addMediaDevice(route, activeSession);
+ }
+
+ // In practice, mMediaDevices should always have at least one route.
+ if (!mMediaDevices.isEmpty()) {
+ // First device on the list is always the first selected route.
+ mCurrentConnectedDevice = mMediaDevices.get(0);
}
}
- private synchronized List<MediaRoute2Info> getAvailableRoutes() {
+
+ private synchronized List<MediaRoute2Info> getAvailableRoutes(
+ RoutingSessionInfo activeSession) {
List<MediaRoute2Info> availableRoutes = new ArrayList<>();
- RoutingSessionInfo activeSession = getActiveRoutingSession();
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(activeSession);
availableRoutes.addAll(selectedRoutes);
@@ -562,7 +580,7 @@ public abstract class InfoMediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@VisibleForTesting
- void addMediaDevice(MediaRoute2Info route) {
+ void addMediaDevice(MediaRoute2Info route, RoutingSessionInfo activeSession) {
final int deviceType = route.getType();
MediaDevice mediaDevice = null;
switch (deviceType) {
@@ -627,14 +645,10 @@ public abstract class InfoMediaManager {
break;
}
- if (mediaDevice != null
- && getActiveRoutingSession().getSelectedRoutes().contains(route.getId())) {
- mediaDevice.setState(STATE_SELECTED);
- if (mCurrentConnectedDevice == null) {
- mCurrentConnectedDevice = mediaDevice;
- }
- }
if (mediaDevice != null) {
+ if (activeSession.getSelectedRoutes().contains(route.getId())) {
+ mediaDevice.setState(STATE_SELECTED);
+ }
mMediaDevices.add(mediaDevice);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index c4fac358c01a..23063da747af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -62,22 +62,30 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
@Override
protected void startScanOnRouter() {
if (!mIsScanning) {
- mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
mRouterManager.registerScanRequest();
mIsScanning = true;
}
}
@Override
- public void stopScan() {
+ protected void registerRouter() {
+ mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
if (mIsScanning) {
- mRouterManager.unregisterCallback(mMediaRouterCallback);
mRouterManager.unregisterScanRequest();
mIsScanning = false;
}
}
@Override
+ protected void unregisterRouter() {
+ mRouterManager.unregisterCallback(mMediaRouterCallback);
+ }
+
+ @Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
// TODO: b/279555229 - provide real user handle of a caller.
mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 2b8c2dd0d0e3..cf11c6da737f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -63,12 +63,22 @@ import java.util.List;
}
@Override
- public void stopScan() {
+ protected void startScanOnRouter() {
// Do nothing.
}
@Override
- protected void startScanOnRouter() {
+ protected void registerRouter() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void unregisterRouter() {
// Do nothing.
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 9c82cb1ef57d..0dceebab13f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -97,11 +97,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
@Override
protected void startScanOnRouter() {
- mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
- mRouter.registerRouteListingPreferenceUpdatedCallback(
- mExecutor, mRouteListingPreferenceCallback);
- mRouter.registerTransferCallback(mExecutor, mTransferCallback);
- mRouter.registerControllerCallback(mExecutor, mControllerCallback);
if (Flags.enableScreenOffScanning()) {
MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build();
mScanToken.compareAndSet(null, mRouter.requestScan(request));
@@ -111,7 +106,16 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
}
@Override
- public void stopScan() {
+ protected void registerRouter() {
+ mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
+ mRouter.registerRouteListingPreferenceUpdatedCallback(
+ mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerTransferCallback(mExecutor, mTransferCallback);
+ mRouter.registerControllerCallback(mExecutor, mControllerCallback);
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
if (Flags.enableScreenOffScanning()) {
MediaRouter2.ScanToken token = mScanToken.getAndSet(null);
if (token != null) {
@@ -120,6 +124,10 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
} else {
mRouter.stopScan();
}
+ }
+
+ @Override
+ protected void unregisterRouter() {
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 0931b685d967..5136e260489b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -72,7 +72,6 @@ public class UtilsTest {
private static final String PERCENTAGE_49 = "49%";
private static final String PERCENTAGE_50 = "50%";
private static final String PERCENTAGE_100 = "100%";
- private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
private AudioManager mAudioManager;
private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1246fd85ee16..f197f9ee0baf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
@@ -25,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -44,6 +46,9 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class BluetoothUtilsTest {
@@ -55,6 +60,16 @@ public class BluetoothUtilsTest {
private AudioManager mAudioManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock
+ private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -72,6 +87,9 @@ public class BluetoothUtilsTest {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
}
@Test
@@ -432,6 +450,30 @@ public class BluetoothUtilsTest {
}
@Test
+ public void testIsBroadcasting_broadcastEnabled_returnTrue() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isEqualTo(true);
+ }
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index d85d2534f856..d7938670c598 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -86,6 +86,41 @@ public class InfoMediaManagerTest {
private static final String TEST_DUPLICATED_ID_2 = "test_duplicated_id_2";
private static final String TEST_DUPLICATED_ID_3 = "test_duplicated_id_3";
+ private static final String TEST_SYSTEM_ROUTE_ID = "TEST_SYSTEM_ROUTE_ID";
+ private static final String TEST_BLUETOOTH_ROUTE_ID = "TEST_BT_ROUTE_ID";
+
+ private static final RoutingSessionInfo TEST_SYSTEM_ROUTING_SESSION =
+ new RoutingSessionInfo.Builder("FAKE_SYSTEM_ROUTING_SESSION_ID", TEST_PACKAGE_NAME)
+ .addSelectedRoute(TEST_SYSTEM_ROUTE_ID)
+ .addTransferableRoute(TEST_BLUETOOTH_ROUTE_ID)
+ .setSystemSession(true)
+ .build();
+
+ private static final MediaRoute2Info TEST_SELECTED_SYSTEM_ROUTE =
+ new MediaRoute2Info.Builder(TEST_SYSTEM_ROUTE_ID, "SELECTED_SYSTEM_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .build();
+
+ private static final MediaRoute2Info TEST_BLUETOOTH_ROUTE =
+ new MediaRoute2Info.Builder(TEST_BLUETOOTH_ROUTE_ID, "BLUETOOTH_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .setType(TYPE_BLUETOOTH_A2DP)
+ .setAddress("00:00:00:00:00:00")
+ .build();
+
+ private static final RoutingSessionInfo TEST_REMOTE_ROUTING_SESSION =
+ new RoutingSessionInfo.Builder("FAKE_REMOTE_ROUTING_SESSION_ID", TEST_PACKAGE_NAME)
+ .addSelectedRoute(TEST_ID_1)
+ .build();
+
+ private static final MediaRoute2Info TEST_REMOTE_ROUTE =
+ new MediaRoute2Info.Builder(TEST_ID_1, "REMOTE_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .build();
+
@Mock
private MediaRouter2Manager mRouterManager;
@Mock
@@ -127,7 +162,10 @@ public class InfoMediaManagerTest {
RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
mInfoMediaManager.mRouterManager = mRouterManager;
// Since test is running in Robolectric, return a fake session to avoid NPE.
- when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo));
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any()))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
mInfoMediaManager.startScan();
mInfoMediaManager.stopScan();
@@ -167,52 +205,27 @@ public class InfoMediaManagerTest {
@Test
public void onSessionReleased_shouldUpdateConnectedDevice() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo1);
- final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo2);
-
- final List<String> selectedRoutesSession1 = new ArrayList<>();
- selectedRoutesSession1.add(TEST_ID_1);
- when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
-
- final List<String> selectedRoutesSession2 = new ArrayList<>();
- selectedRoutesSession2.add(TEST_ID_2);
- when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
-
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
- final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
- when(info1.getId()).thenReturn(TEST_ID_1);
- when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
- when(info2.getId()).thenReturn(TEST_ID_2);
- when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info1);
- routes.add(info2);
- mShadowRouter2Manager.setAllRoutes(routes);
- mShadowRouter2Manager.setTransferableRoutes(routes);
+ mInfoMediaManager.mRouterManager = mRouterManager;
- final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
- assertThat(mediaDevice1).isNull();
- final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
- assertThat(mediaDevice2).isNull();
+ // Active routing session is last one in list.
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(TEST_SYSTEM_ROUTING_SESSION))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
+ when(mRouterManager.getSelectedRoutes(TEST_REMOTE_ROUTING_SESSION))
+ .thenReturn(List.of(TEST_REMOTE_ROUTE));
mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
- final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
- final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
- assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
- // The active routing session is the last one in the list, which maps to infoDevice2.
- assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+ MediaDevice remoteDevice = mInfoMediaManager.findMediaDevice(TEST_REMOTE_ROUTE.getId());
+ assertThat(remoteDevice).isNotNull();
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(remoteDevice);
- routingSessionInfos.remove(sessionInfo2);
- mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
- assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ mInfoMediaManager.mMediaRouterCallback.onSessionReleased(TEST_REMOTE_ROUTING_SESSION);
+ MediaDevice systemRoute = mInfoMediaManager.findMediaDevice(TEST_SYSTEM_ROUTE_ID);
+ assertThat(systemRoute).isNotNull();
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(systemRoute);
}
@Test
@@ -770,18 +783,16 @@ public class InfoMediaManagerTest {
@Test
public void onSessionUpdated_shouldDispatchDeviceListAdded() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ // Since test is running in Robolectric, return a fake session to avoid NPE.
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any()))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
mInfoMediaManager.registerCallback(mCallback);
- mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
+ mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION);
verify(mCallback).onDeviceListAdded(any());
}
@@ -795,19 +806,19 @@ public class InfoMediaManagerTest {
when(route2Info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
when(route2Info.getId()).thenReturn(TEST_ID);
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof InfoMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_USB_DEVICE);
when(route2Info.getId()).thenReturn(TEST_ID);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_WIRED_HEADSET);
when(route2Info.getId()).thenReturn(TEST_ID);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
@@ -818,12 +829,12 @@ public class InfoMediaManagerTest {
when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
.thenReturn(cachedDevice);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof BluetoothMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
}
@@ -841,34 +852,35 @@ public class InfoMediaManagerTest {
.thenReturn(null);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.size()).isEqualTo(0);
}
@Test
- public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() {
- final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+ public void onRoutesUpdated_setsFirstSelectedRouteAsCurrentConnectedDevice() {
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
mock(CachedBluetoothDeviceManager.class);
+
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
+ RoutingSessionInfo selectedBtSession =
+ new RoutingSessionInfo.Builder(TEST_SYSTEM_ROUTING_SESSION)
+ .clearSelectedRoutes()
+ .clearTransferableRoutes()
+ .addSelectedRoute(TEST_BLUETOOTH_ROUTE_ID)
+ .addTransferableRoute(TEST_SYSTEM_ROUTE_ID)
+ .build();
- when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
- when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
- when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
- when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
- when(route2Info.getId()).thenReturn(TEST_ID);
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME))
+ .thenReturn(List.of(selectedBtSession));
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_BLUETOOTH_ROUTE));
when(mLocalBluetoothManager.getCachedDeviceManager())
.thenReturn(cachedBluetoothDeviceManager);
when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
.thenReturn(cachedDevice);
mInfoMediaManager.mRouterManager = mRouterManager;
- mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
MediaDevice device = mInfoMediaManager.mMediaDevices.get(0);
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index bf4f60d84e4d..e9c267284e91 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -32,6 +32,7 @@ android_library {
"unsupportedappusage",
],
static_libs: [
+ "aconfig_demo_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
"SettingsLibDeviceStateRotationLock",
@@ -87,6 +88,7 @@ android_test {
aconfig_declarations {
name: "device_config_service_flags",
package: "com.android.providers.settings",
+ container: "system",
srcs: [
"src/com/android/providers/settings/device_config_service.aconfig",
],
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 38a3a2aad609..a33c160924e7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -258,6 +258,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED,
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
Settings.Secure.STYLUS_BUTTONS_ENABLED,
Settings.Secure.STYLUS_HANDWRITING_ENABLED,
Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 252cb8fa8f8b..1bff59289326 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -416,6 +416,7 @@ public class SecureSettingsValidators {
BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.GLANCEABLE_HUB_ENABLED, new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.STYLUS_BUTTONS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4e4c22fadcfd..68167e1598e0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -60,16 +60,17 @@ import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -165,8 +166,8 @@ final class SettingsState {
private static final String STORAGE_MIGRATION_FLAG =
"core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
- private static final String STORAGE_MIGRATION_LOG =
- "/metadata/aconfig/flags/storage_migration.log";
+ private static final String STORAGE_MIGRATION_MARKER_FILE =
+ "/metadata/aconfig/storage_test_mission_1";
/**
* This tag is applied to all aconfig default value-loaded flags.
@@ -1126,7 +1127,7 @@ final class SettingsState {
Slog.i(LOG_TAG, "[PERSIST END]");
}
} catch (Throwable t) {
- Slog.wtf(LOG_TAG, "Failed to write settings, restoring old file", t);
+ Slog.e(LOG_TAG, "Failed to write settings, restoring old file", t);
if (t instanceof IOException) {
if (t.getMessage().contains("Couldn't create directory")) {
if (DEBUG) {
@@ -1467,16 +1468,29 @@ final class SettingsState {
}
}
- if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) {
- File file = new File(STORAGE_MIGRATION_LOG);
- if (!file.exists()) {
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) {
- final long timestamp = System.currentTimeMillis();
- String entry = String.format("%d | Log init", timestamp);
- writer.write(entry);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "failed to write storage migration file", e);
+ if (isConfigSettingsKey(mKey) && name != null
+ && name.equals(STORAGE_MIGRATION_FLAG)) {
+ if (value.equals("true")) {
+ Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
+ if (!Files.exists(path)) {
+ Files.createFile(path);
+ }
+
+ Set<PosixFilePermission> perms =
+ Files.readAttributes(path, PosixFileAttributes.class).permissions();
+ perms.add(PosixFilePermission.OWNER_WRITE);
+ perms.add(PosixFilePermission.OWNER_READ);
+ perms.add(PosixFilePermission.GROUP_READ);
+ perms.add(PosixFilePermission.OTHERS_READ);
+ try {
+ Files.setPosixFilePermissions(path, perms);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "failed to set permissions on migration marker", e);
+ }
+ } else {
+ java.nio.file.Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
+ if (Files.exists(path)) {
+ Files.delete(path);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index c572bdb57c6a..d20fbf591a25 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -1,4 +1,5 @@
package: "com.android.providers.settings"
+container: "system"
flag {
name: "support_overrides"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 58040716db3e..b94e224850aa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -896,7 +896,6 @@
<!-- Permissions required for CTS test - CtsVoiceInteractionTestCases -->
<uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" />
- <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" />
<uses-permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" />
<uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 42952de1b2b9..5ac0e449b8e1 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -50,7 +50,6 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback;
-import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
import android.os.BugreportParams;
import android.os.Bundle;
import android.os.FileUtils;
@@ -169,6 +168,8 @@ public class BugreportProgressService extends Service {
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
static final String EXTRA_INFO = "android.intent.extra.INFO";
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
private static final int MSG_SERVICE_COMMAND = 1;
private static final int MSG_DELAYED_SCREENSHOT = 2;
@@ -634,9 +635,10 @@ public class BugreportProgressService extends Service {
long nonce = intent.getLongExtra(EXTRA_BUGREPORT_NONCE, 0);
String baseName = getBugreportBaseName(bugreportType);
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+ Uri extraAttachment = intent.getParcelableExtra(EXTRA_EXTRA_ATTACHMENT_URI, Uri.class);
- BugreportInfo info = new BugreportInfo(mContext, baseName, name,
- shareTitle, shareDescription, bugreportType, mBugreportsDir, nonce);
+ BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
+ shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachment);
synchronized (mLock) {
if (info.bugreportFile.exists()) {
Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
@@ -1184,6 +1186,10 @@ public class BugreportProgressService extends Service {
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
attachments.add(screenshotUri);
}
+ if (info.extraAttachment != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, info.extraAttachment));
+ attachments.add(info.extraAttachment);
+ }
intent.setClipData(clipData);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
@@ -2042,6 +2048,9 @@ public class BugreportProgressService extends Service {
*/
final long nonce;
+ @Nullable
+ public Uri extraAttachment = null;
+
private final Object mLock = new Object();
/**
@@ -2049,7 +2058,8 @@ public class BugreportProgressService extends Service {
*/
BugreportInfo(Context context, String baseName, String name,
@Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce) {
+ @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
+ @Nullable Uri extraAttachment) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2058,6 +2068,7 @@ public class BugreportProgressService extends Service {
this.nonce = nonce;
this.baseName = baseName;
this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.extraAttachment = extraAttachment;
}
void createBugreportFile() {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 74d61ca199dd..40db52eec81b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@ filegroup {
"src-release/**/*.kt",
"src-release/**/*.java",
],
+ visibility: ["//visibility:private"],
}
filegroup {
@@ -65,6 +66,7 @@ filegroup {
"src-debug/**/*.kt",
"src-debug/**/*.java",
],
+ visibility: ["//visibility:private"],
}
//Create a library to expose SystemUI's resources to other modules.
@@ -105,6 +107,7 @@ android_library {
},
use_resource_processor: true,
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-res",
"WifiTrackerLib",
"WindowManager-Shell",
@@ -117,7 +120,7 @@ android_library {
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
@@ -145,7 +148,7 @@ android_library {
"device_state_flags_lib",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"monet",
@@ -156,7 +159,7 @@ android_library {
"lottie",
"LowLightDreamLib",
"TraceurCommon",
- "motion_tool_lib",
+ "//frameworks/libs/systemui:motion_tool_lib",
"notification_flags_lib",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
@@ -249,6 +252,9 @@ android_library {
resource_dirs: [
"tests/res",
],
+ asset_dirs: [
+ "tests/goldens",
+ ],
static_libs: [
"SystemUI-res",
"WifiTrackerLib",
@@ -260,7 +266,7 @@ android_library {
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"flag-junit-base",
"platform-parametric-runner-lib",
"androidx.viewpager2_viewpager2",
@@ -290,7 +296,7 @@ android_library {
"kotlinx-coroutines-core",
"kotlinx_coroutines_test",
"kotlin-reflect",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"metrics-helper-lib",
@@ -304,7 +310,7 @@ android_library {
"jsr330",
"WindowManager-Shell",
"LowLightDreamLib",
- "motion_tool_lib",
+ "//frameworks/libs/systemui:motion_tool_lib",
"androidx.core_core-animation-testing",
"androidx.compose.ui_ui",
"flag-junit",
@@ -341,6 +347,7 @@ android_library {
"compose/facade/enabled/src/**/*.kt",
],
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-tests-base",
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
@@ -349,6 +356,8 @@ android_library {
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
+ "platform-screenshot-diff-core",
+ "PlatformMotionTesting",
"SystemUICustomizationTestUtils",
"androidx.compose.runtime_runtime",
"kosmos",
@@ -393,6 +402,7 @@ android_app {
"compose/facade/enabled/src/**/*.kt",
],
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-tests-base",
"androidx.compose.runtime_runtime",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
index f74e59abeca5..0ff856e0b91e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
@@ -5,6 +5,7 @@ package {
aconfig_declarations {
name: "com_android_a11y_menu_flags",
package: "com.android.systemui.accessibility.accessibilitymenu",
+ container: "system",
srcs: [
"accessibility.aconfig",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index f5db6a4c4573..d868d5c7c4c4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui.accessibility.accessibilitymenu"
+container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 15c2c17fd7f0..2a32b5854425 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -36,6 +36,7 @@ package {
aconfig_declarations {
name: "com_android_systemui_flags",
package: "com.android.systemui",
+ container: "system",
srcs: [
"*.aconfig",
],
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 866aa8945525..8137e408ba39 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 7cc0c83abfea..bd1a442b874f 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
@@ -14,4 +15,4 @@ flag {
namespace: "biometrics_framework"
description: "Refactors Biometric Prompt to use a ConstraintLayout"
bug: "288175072"
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig
index 2c6ff979cc7f..2e9af7e3a763 100644
--- a/packages/SystemUI/aconfig/communal.aconfig
+++ b/packages/SystemUI/aconfig/communal.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
flag {
name: "communal_hub"
diff --git a/packages/SystemUI/aconfig/cross_device_control.aconfig b/packages/SystemUI/aconfig/cross_device_control.aconfig
index d3f14c129a0b..5f9a4f42b546 100644
--- a/packages/SystemUI/aconfig/cross_device_control.aconfig
+++ b/packages/SystemUI/aconfig/cross_device_control.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
flag {
name: "legacy_le_audio_sharing"
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index 7bbe82c212e7..46eb9e1c2330 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
flag {
name: "predictive_back_sysui"
@@ -26,4 +27,4 @@ flag {
namespace: "systemui"
description: "Enable Predictive Back Animation for SysUI dialogs"
bug: "327721544"
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index af89f63cb4e3..6810aac92925 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1,4 +1,5 @@
package: "com.android.systemui"
+container: "system"
flag {
name: "example_flag"
@@ -25,6 +26,21 @@ flag {
}
flag {
+ name: "refactor_keyguard_dismiss_intent"
+ namespace: "systemui"
+ description: "Update how keyguard dismiss intents are stored."
+ bug: "275069969"
+}
+
+flag {
+
+ name: "notification_heads_up_cycling"
+ namespace: "systemui"
+ description: "Heads-up notification cycling animation for the Notification Avalanche feature."
+ bug: "316404716"
+}
+
+flag {
name: "notification_minimalism_prototype"
namespace: "systemui"
description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
@@ -708,3 +724,10 @@ flag {
" Compose for the UI."
bug: "325099249"
}
+
+flag {
+ name: "keyboard_docking_indicator"
+ namespace: "systemui"
+ description: "Glow bar indicator reveals upon keyboard docking."
+ bug: "324600132"
+}
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index a6d750f8f6b4..dec664fa7a14 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -47,7 +47,8 @@ android_library {
"com_android_systemui_flags_lib",
"SystemUIShaderLib",
"WindowManager-Shell-shared",
- "animationlib",
+ "//frameworks/libs/systemui:animationlib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index ea1cb3441215..9ce30fd0c6cb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -790,7 +790,7 @@ class ActivityTransitionAnimator(
controller,
endState,
windowBackgroundColor,
- fadeOutWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
+ fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
drawHole = !controller.isBelowAnimatingWindow,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index 24cc8a4cfcec..b89ebfcb3675 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -916,6 +916,12 @@ private class AnimatedDialog(
endController.transitionContainer = value
}
+ // We tell TransitionController that this is always a launch, and handle the launch
+ // vs return logic internally.
+ // TODO(b/323863002): maybe move the launch vs return logic out of this class and
+ // delegate it to TransitionController?
+ override val isLaunching: Boolean = true
+
override fun createAnimatorState(): TransitionAnimator.State {
return startController.createAnimatorState()
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 3f57f88a13d3..9ad0fc53648f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -64,6 +64,7 @@ constructor(
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
/** The container to which we will add the ghost view and expanding background. */
override var transitionContainer = ghostedView.rootView as ViewGroup
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 5e4276ce3dd2..9bf6b346d16d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -28,7 +28,9 @@ import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
+import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators.LINEAR
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import kotlin.math.roundToInt
private const val TAG = "TransitionAnimator"
@@ -70,13 +72,14 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
interface Controller {
/**
* The container in which the view that started the animation will be animating together
- * with the opening window.
+ * with the opening or closing window.
*
* This will be used to:
* - Get the associated [Context].
- * - Compute whether we are expanding fully above the transition container.
- * - Get to overlay to which we initially put the window background layer, until the opening
- * window is made visible (see [openingWindowSyncView]).
+ * - Compute whether we are expanding to or contracting from fully above the transition
+ * container.
+ * - Get the overlay into which we put the window background layer, while the animating
+ * window is not visible (see [openingWindowSyncView]).
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
@@ -84,12 +87,17 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
*/
var transitionContainer: ViewGroup
+ /** Whether the animation being controlled is a launch or a return. */
+ val isLaunching: Boolean
+
/**
- * The [View] with which the opening app window should be synchronized with once it starts
- * to be visible.
+ * If [isLaunching], the [View] with which the opening app window should be synchronized
+ * once it starts to be visible. Otherwise, the [View] with which the closing app window
+ * should be synchronized until it stops being visible.
*
* We will also move the window background layer to this view's overlay once the opening
- * window is visible.
+ * window is visible (if [isLaunching]), or from this view's overlay once the closing window
+ * stop being visible (if ![isLaunching]).
*
* If null, this will default to [transitionContainer].
*/
@@ -203,17 +211,56 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
* layer with [windowBackgroundColor] will fade in then (optionally) fade out above the
* expanding view, and should be the same background color as the opening (or closing) window.
*
- * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
- * the second half of the animation, and will have SRC blending mode (ultimately punching a hole
- * in the [transition container][Controller.transitionContainer]) iff [drawHole] is true.
+ * If [fadeWindowBackgroundLayer] is true, then this intermediary layer will fade out during the
+ * second half of the animation (if [Controller.isLaunching] or fade in during the first half of
+ * the animation (if ![Controller.isLaunching]), and will have SRC blending mode (ultimately
+ * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole]
+ * is true.
*/
fun startAnimation(
controller: Controller,
endState: State,
windowBackgroundColor: Int,
- fadeOutWindowBackgroundLayer: Boolean = true,
+ fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
): Animation {
+ if (!controller.isLaunching) checkReturnAnimationFrameworkFlag()
+
+ // We add an extra layer with the same color as the dialog/app splash screen background
+ // color, which is usually the same color of the app background. We first fade in this layer
+ // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
+ // transition container and reveal the opening window.
+ val windowBackgroundLayer =
+ GradientDrawable().apply {
+ setColor(windowBackgroundColor)
+ alpha = 0
+ }
+
+ val animator =
+ createAnimator(
+ controller,
+ endState,
+ windowBackgroundLayer,
+ fadeWindowBackgroundLayer,
+ drawHole
+ )
+ animator.start()
+
+ return object : Animation {
+ override fun cancel() {
+ animator.cancel()
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun createAnimator(
+ controller: Controller,
+ endState: State,
+ windowBackgroundLayer: GradientDrawable,
+ fadeWindowBackgroundLayer: Boolean = true,
+ drawHole: Boolean = false
+ ): ValueAnimator {
val state = controller.createAnimatorState()
// Start state.
@@ -255,31 +302,24 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
val transitionContainer = controller.transitionContainer
val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
- // We add an extra layer with the same color as the dialog/app splash screen background
- // color, which is usually the same color of the app background. We first fade in this layer
- // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
- // transition container and reveal the opening window.
- val windowBackgroundLayer =
- GradientDrawable().apply {
- setColor(windowBackgroundColor)
- alpha = 0
- }
-
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = timings.totalDuration
animator.interpolator = LINEAR
// Whether we should move the [windowBackgroundLayer] into the overlay of
- // [Controller.openingWindowSyncView] once the opening app window starts to be visible.
+ // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or
+ // from it once the closing app window stops being visible.
+ // This is necessary as a one-off sync so we can avoid syncing at every frame, especially
+ // in complex interactions like launching an activity from a dialog. See
+ // b/214961273#comment2 for more details.
val openingWindowSyncView = controller.openingWindowSyncView
val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
- val moveBackgroundLayerWhenAppIsVisible =
+ val moveBackgroundLayerWhenAppVisibilityChanges =
openingWindowSyncView != null &&
openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
val transitionContainerOverlay = transitionContainer.overlay
- var cancelled = false
var movedBackgroundLayer = false
animator.addListener(
@@ -293,7 +333,11 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
// Add the drawable to the transition container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
- transitionContainerOverlay.add(windowBackgroundLayer)
+ if (controller.isLaunching || openingWindowSyncViewOverlay == null) {
+ transitionContainerOverlay.add(windowBackgroundLayer)
+ } else {
+ openingWindowSyncViewOverlay.add(windowBackgroundLayer)
+ }
}
override fun onAnimationEnd(animation: Animator) {
@@ -303,7 +347,7 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
controller.onTransitionAnimationEnd(isExpandingFullyAbove)
transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppIsVisible) {
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
}
}
@@ -311,12 +355,6 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
)
animator.addUpdateListener { animation ->
- if (cancelled) {
- // TODO(b/184121838): Cancel the animator directly instead of just skipping the
- // update.
- return@addUpdateListener
- }
-
maybeUpdateEndState()
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
@@ -338,20 +376,34 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress)
- // The expanding view can/should be hidden once it is completely covered by the opening
- // window.
state.visible =
- getProgress(
- timings,
- linearProgress,
- timings.contentBeforeFadeOutDelay,
- timings.contentBeforeFadeOutDuration
- ) < 1
-
- if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) {
- // The expanding view is not visible, so the opening app is visible. If this is the
- // first frame when it happens, trigger a one-off sync and move the background layer
- // in its new container.
+ if (controller.isLaunching) {
+ // The expanding view can/should be hidden once it is completely covered by the
+ // opening window.
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ ) < 1
+ } else {
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ ) > 0
+ }
+
+ if (
+ controller.isLaunching &&
+ moveBackgroundLayerWhenAppVisibilityChanges &&
+ !state.visible &&
+ !movedBackgroundLayer
+ ) {
+ // The expanding view is not visible, so the opening app is visible. If this is
+ // the first frame when it happens, trigger a one-off sync and move the
+ // background layer in its new container.
movedBackgroundLayer = true
transitionContainerOverlay.remove(windowBackgroundLayer)
@@ -362,6 +414,25 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
openingWindowSyncView,
then = {}
)
+ } else if (
+ !controller.isLaunching &&
+ moveBackgroundLayerWhenAppVisibilityChanges &&
+ state.visible &&
+ !movedBackgroundLayer
+ ) {
+ // The contracting view is now visible, so the closing app is not. If this is
+ // the first frame when it happens, trigger a one-off sync and move the
+ // background layer in its new container.
+ movedBackgroundLayer = true
+
+ openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer)
+ transitionContainerOverlay.add(windowBackgroundLayer)
+
+ ViewRootSync.synchronizeNextDraw(
+ openingWindowSyncView,
+ transitionContainer,
+ then = {}
+ )
}
val container =
@@ -376,19 +447,14 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
state,
linearProgress,
container,
- fadeOutWindowBackgroundLayer,
- drawHole
+ fadeWindowBackgroundLayer,
+ drawHole,
+ controller.isLaunching
)
controller.onTransitionAnimationProgress(state, progress, linearProgress)
}
- animator.start()
- return object : Animation {
- override fun cancel() {
- cancelled = true
- animator.cancel()
- }
- }
+ return animator
}
/** Return whether we are expanding fully above the [transitionContainer]. */
@@ -405,8 +471,9 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
state: State,
linearProgress: Float,
transitionContainer: View,
- fadeOutWindowBackgroundLayer: Boolean,
- drawHole: Boolean
+ fadeWindowBackgroundLayer: Boolean,
+ drawHole: Boolean,
+ isLaunching: Boolean
) {
// Update position.
transitionContainer.getLocationOnScreen(transitionContainerLocation)
@@ -437,27 +504,64 @@ class TransitionAnimator(private val timings: Timings, private val interpolators
timings.contentBeforeFadeOutDelay,
timings.contentBeforeFadeOutDuration
)
- if (fadeInProgress < 1) {
- val alpha =
- interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- } else if (fadeOutWindowBackgroundLayer) {
- val fadeOutProgress =
- getProgress(
- timings,
- linearProgress,
- timings.contentAfterFadeInDelay,
- timings.contentAfterFadeInDuration
- )
- val alpha =
- 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- if (drawHole) {
- drawable.setXfermode(SRC_MODE)
+ if (isLaunching) {
+ if (fadeInProgress < 1) {
+ val alpha =
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+ } else if (fadeWindowBackgroundLayer) {
+ val fadeOutProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
+ val alpha =
+ 1 -
+ interpolators.contentAfterFadeInInterpolator.getInterpolation(
+ fadeOutProgress
+ )
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+
+ if (drawHole) {
+ drawable.setXfermode(SRC_MODE)
+ }
+ } else {
+ drawable.alpha = 0xFF
}
} else {
- drawable.alpha = 0xFF
+ if (fadeInProgress < 1 && fadeWindowBackgroundLayer) {
+ val alpha =
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+
+ if (drawHole) {
+ drawable.setXfermode(SRC_MODE)
+ }
+ } else {
+ val fadeOutProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
+ val alpha =
+ 1 -
+ interpolators.contentAfterFadeInInterpolator.getInterpolation(
+ fadeOutProgress
+ )
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+ drawable.setXfermode(null)
+ }
+ }
+ }
+
+ private fun checkReturnAnimationFrameworkFlag() {
+ check(returnAnimationFrameworkLibrary()) {
+ "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is disabled"
}
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 974ee3a40903..c7f0a965206e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -168,6 +168,9 @@ internal class ExpandableControllerImpl(
override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+ // TODO(b/323863002): update to be dependant on usage.
+ override val isLaunching: Boolean = true
+
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
animatorState.value = null
}
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 ed8027756f47..32c03136bf8e 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
@@ -17,9 +17,11 @@
package com.android.systemui.communal.ui.compose
import android.appwidget.AppWidgetHostView
+import android.content.res.Configuration
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@@ -92,6 +94,7 @@ import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
@@ -110,8 +113,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -364,7 +365,7 @@ private fun ScrollOnUpdatedLiveContentEffect(
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -834,6 +835,13 @@ private fun WidgetContent(
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ var widgetId: Int by remember { mutableStateOf(-1) }
+ var configuration: Configuration? by remember { mutableStateOf(null) }
+
+ // In addition to returning the current configuration, this also causes a recompose on
+ // configuration change.
+ val currentConfiguration = LocalConfiguration.current
+
Box(
modifier =
modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
@@ -849,18 +857,48 @@ private fun WidgetContent(
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
- model.appWidgetHost
- .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
- .apply {
- updateAppWidgetSize(Bundle.EMPTY, listOf(size))
- // Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box.
- setPadding(0)
- }
+ // This FrameLayout becomes the container view for the AppWidgetHostView we add as a
+ // child in update below. Having a container view allows for updating the contained
+ // host view as needed (e.g. on configuration changes).
+ FrameLayout(context).apply {
+ layoutParams =
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ setPadding(0)
+ }
+ },
+ update = { widgetContainer: ViewGroup ->
+ if (widgetId == model.appWidgetId && currentConfiguration.equals(configuration)) {
+ return@AndroidView
+ }
+
+ // Add the widget's host view to the FrameLayout parent (after removing any
+ // previously added host view).
+ widgetContainer.removeAllViews()
+ widgetContainer.addView(
+ model.appWidgetHost
+ .createViewForCommunal(
+ widgetContainer.context,
+ model.appWidgetId,
+ model.providerInfo
+ )
+ .apply {
+ updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+ // Remove the extra padding applied to AppWidgetHostView to allow
+ // widgets to occupy the entire box.
+ setPadding(0)
+ }
+ )
+
+ widgetId = model.appWidgetId
+ configuration = currentConfiguration
},
// For reusing composition in lazy lists.
onReset = {},
)
+
if (
viewModel is CommunalEditModeViewModel &&
model.reconfigurable &&
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index 7a73c58ba193..8129e41b4977 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -71,7 +71,6 @@ private fun rememberBurnInParameters(
return remember(clock, topInset, topmostTop) {
BurnInParameters(
- clockControllerProvider = { clock },
topInset = topInset,
minViewY = topmostTop,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index c008a1a4d192..28e92aad914a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -86,16 +86,21 @@ constructor(
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 091a43923a6e..b8f00dce53df 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -86,16 +86,21 @@ constructor(
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index 09d76a341442..cba54531713b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -149,6 +149,7 @@ constructor(
if (areNotificationsVisible) {
with(notificationSection) {
Notifications(
+ burnInParams = burnIn.parameters,
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
)
}
@@ -375,6 +376,7 @@ constructor(
)
}
Notifications(
+ burnInParams = burnIn.parameters,
modifier =
Modifier.fillMaxHeight()
.weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 97d5b41000de..467dbca759c8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,6 +32,7 @@ import androidx.core.content.res.ResourcesCompat
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
@@ -43,6 +44,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.Flow
@@ -54,6 +56,7 @@ constructor(
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) {
/**
* Renders a single lockscreen shortcut.
@@ -161,6 +164,7 @@ constructor(
transitionAlpha,
falsingManager,
vibratorHelper,
+ mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 1c938a6c19a5..eb389e609cc5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -34,7 +34,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.contains
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
-import com.android.systemui.customization.R as customizationR
import com.android.systemui.customization.R
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
@@ -65,32 +64,25 @@ constructor(
return
}
val context = LocalContext.current
- MovableElement(key = smallClockElementKey, modifier = modifier) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
- }
- },
- update = {
- it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
- },
- modifier =
- Modifier.height(dimensionResource(R.dimen.small_clock_height))
- .padding(
- horizontal =
- dimensionResource(customizationR.dimen.clock_padding_start)
- )
- .padding(top = { viewModel.getSmallClockTopMargin(context) })
- .onTopPlacementChanged(onTopChanged)
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
+ }
+ },
+ update = { it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) },
+ modifier =
+ modifier
+ .height(dimensionResource(R.dimen.small_clock_height))
+ .padding(horizontal = dimensionResource(R.dimen.clock_padding_start))
+ .padding(top = { viewModel.getSmallClockTopMargin(context) })
+ .onTopPlacementChanged(onTopChanged)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ )
+ .element(smallClockElementKey),
+ )
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 9f02201f1d81..48684a02bd19 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -34,6 +34,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -50,12 +51,14 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
class LockSection
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
@@ -93,6 +96,7 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
+ mainImmediateDispatcher,
)
}
} else {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index fa0a1c4663b1..f48fa88b9722 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -32,8 +32,11 @@ import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -48,6 +51,7 @@ class NotificationSection
@Inject
constructor(
private val viewModel: NotificationsPlaceholderViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
@@ -77,8 +81,12 @@ constructor(
)
}
+ /**
+ * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
+ * adjustment
+ */
@Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
val shouldUseSplitNotificationShade by
lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
val areNotificationsVisible by
@@ -94,12 +102,24 @@ constructor(
return
}
- NotificationStack(
+ ConstrainedNotificationStack(
viewModel = viewModel,
modifier =
- modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
- Modifier.padding(top = splitShadeTopMargin)
- },
+ modifier
+ .fillMaxWidth()
+ .thenIf(shouldUseSplitNotificationShade) {
+ Modifier.padding(top = splitShadeTopMargin)
+ }
+ .let {
+ if (burnInParams == null) {
+ it
+ } else {
+ it.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ )
+ }
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index cda43476610e..579e837a62d0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -36,12 +36,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
@@ -60,7 +62,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import com.android.compose.animation.scene.ElementKey
@@ -68,12 +69,12 @@ import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
-import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -81,7 +82,8 @@ import kotlin.math.roundToInt
object Notifications {
object Elements {
val NotificationScrim = ElementKey("NotificationScrim")
- val NotificationPlaceholder = ElementKey("NotificationPlaceholder")
+ val NotificationStackPlaceholder = ElementKey("NotificationStackPlaceholder")
+ val HeadsUpNotificationPlaceholder = ElementKey("HeadsUpNotificationPlaceholder")
val ShelfSpace = ElementKey("ShelfSpace")
}
@@ -91,12 +93,6 @@ object Notifications {
const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
}
-
- enum class Form {
- HunFromTop,
- Stack,
- HunFromBottom,
- }
}
/**
@@ -109,24 +105,48 @@ fun SceneScope.HeadsUpNotificationSpace(
modifier: Modifier = Modifier,
isPeekFromBottom: Boolean = false,
) {
- NotificationPlaceholder(
- viewModel = viewModel,
- form = if (isPeekFromBottom) Form.HunFromBottom else Form.HunFromTop,
- modifier = modifier,
- )
+ val headsUpHeight = viewModel.headsUpHeight.collectAsState()
+
+ Element(
+ Notifications.Elements.HeadsUpNotificationPlaceholder,
+ modifier =
+ modifier
+ .height { headsUpHeight.value.roundToInt() }
+ .fillMaxWidth()
+ .debugBackground(viewModel, DEBUG_HUN_COLOR)
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
+ val boundsInWindow = coordinates.boundsInWindow()
+ debugLog(viewModel) {
+ "HUNS onGloballyPositioned:" +
+ " size=${coordinates.size}" +
+ " bounds=$boundsInWindow"
+ }
+ viewModel.onHeadsUpTopChanged(boundsInWindow.top)
+ }
+ ) {
+ content {}
+ }
}
/** Adds the space where notification stack should appear in the scene. */
@Composable
-fun SceneScope.NotificationStack(
+fun SceneScope.ConstrainedNotificationStack(
viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
- NotificationPlaceholder(
- viewModel = viewModel,
- form = Form.Stack,
- modifier = modifier,
- )
+ Box(
+ modifier =
+ modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) }
+ ) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ modifier = Modifier.fillMaxSize(),
+ )
+ HeadsUpNotificationSpace(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.TopCenter),
+ )
+ }
}
/**
@@ -169,6 +189,9 @@ fun SceneScope.NotificationScrollingStack(
// entire height of the scrim is visible on screen.
val scrimOffset = remember { mutableStateOf(0f) }
+ // set the bounds to null when the scrim disappears
+ DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } }
+
val minScrimTop = with(density) { ShadeHeader.Dimensions.CollapsedHeight.toPx() }
// The minimum offset for the scrim. The scrim is considered fully expanded when it
@@ -235,6 +258,22 @@ fun SceneScope.NotificationScrollingStack(
.let { scrimRounding.value.toRoundedCornerShape(it) }
clip = true
}
+ .onGloballyPositioned { coordinates ->
+ val boundsInWindow = coordinates.boundsInWindow()
+ debugLog(viewModel) {
+ "SCRIM onGloballyPositioned:" +
+ " size=${coordinates.size}" +
+ " bounds=$boundsInWindow"
+ }
+ viewModel.onScrimBoundsChanged(
+ ShadeScrimBounds(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ )
+ }
) {
// Creates a cutout in the background scrim in the shape of the notifications scrim.
// Only visible when notif scrim alpha < 1, during shade expansion.
@@ -254,11 +293,10 @@ fun SceneScope.NotificationScrollingStack(
} else 1f
}
.background(MaterialTheme.colorScheme.surface)
- .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ .debugBackground(viewModel, DEBUG_BOX_COLOR)
) {
NotificationPlaceholder(
viewModel = viewModel,
- form = Form.Stack,
modifier =
Modifier.verticalNestedScrollToScene(
topBehavior = NestedScrollBehavior.EdgeWithPreview,
@@ -284,6 +322,7 @@ fun SceneScope.NotificationScrollingStack(
.height { (stackHeight.value + navBarHeight).roundToInt() },
)
}
+ HeadsUpNotificationSpace(viewModel = viewModel)
}
}
@@ -304,14 +343,10 @@ fun SceneScope.NotificationShelfSpace(
modifier
.element(key = Notifications.Elements.ShelfSpace)
.fillMaxWidth()
- .onSizeChanged { size: IntSize ->
- debugLog(viewModel) { "SHELF onSizeChanged: size=$size" }
- }
.onPlaced { coordinates: LayoutCoordinates ->
debugLog(viewModel) {
("SHELF onPlaced:" +
" size=${coordinates.size}" +
- " position=${coordinates.positionInWindow()}" +
" bounds=${coordinates.boundsInWindow()}")
}
}
@@ -326,32 +361,26 @@ fun SceneScope.NotificationShelfSpace(
@Composable
private fun SceneScope.NotificationPlaceholder(
viewModel: NotificationsPlaceholderViewModel,
- form: Form,
modifier: Modifier = Modifier,
) {
- val elementKey = Notifications.Elements.NotificationPlaceholder
Element(
- elementKey,
+ Notifications.Elements.NotificationStackPlaceholder,
modifier =
modifier
- .debugBackground(viewModel)
- .onSizeChanged { size: IntSize ->
- debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
- }
+ .debugBackground(viewModel, DEBUG_STACK_COLOR)
+ .onSizeChanged { size -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } }
.onGloballyPositioned { coordinates: LayoutCoordinates ->
- viewModel.onContentTopChanged(coordinates.positionInWindow().y)
+ val positionInWindow = coordinates.positionInWindow()
debugLog(viewModel) {
"STACK onGloballyPositioned:" +
" size=${coordinates.size}" +
- " position=${coordinates.positionInWindow()}" +
+ " position=$positionInWindow" +
" bounds=${coordinates.boundsInWindow()}"
}
- val boundsInWindow = coordinates.boundsInWindow()
- viewModel.onBoundsChanged(
- left = boundsInWindow.left,
- top = boundsInWindow.top,
- right = boundsInWindow.right,
- bottom = boundsInWindow.bottom,
+ // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not
+ viewModel.onStackBoundsChanged(
+ top = positionInWindow.y,
+ bottom = positionInWindow.y + coordinates.size.height,
)
}
) {
@@ -388,7 +417,7 @@ private inline fun debugLog(
private fun Modifier.debugBackground(
viewModel: NotificationsPlaceholderViewModel,
- color: Color = DEBUG_COLOR,
+ color: Color,
): Modifier =
if (viewModel.isVisualDebuggingEnabled) {
background(color)
@@ -397,8 +426,8 @@ private fun Modifier.debugBackground(
}
fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
- val topRadius = if (roundTop) radius else 0.dp
- val bottomRadius = if (roundBottom) radius else 0.dp
+ val topRadius = if (isTopRounded) radius else 0.dp
+ val bottomRadius = if (isBottomRounded) radius else 0.dp
return RoundedCornerShape(
topStart = topRadius,
topEnd = topRadius,
@@ -408,4 +437,6 @@ fun ShadeScrimRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
}
private const val TAG = "FlexiNotifs"
-private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f)
+private val DEBUG_STACK_COLOR = Color(1f, 0f, 0f, 0.2f)
+private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f)
+private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
new file mode 100644
index 000000000000..ca6b3434d90e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.composable
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+
+@Composable
+fun BrightnessMirror(
+ viewModel: BrightnessMirrorViewModel,
+ qsSceneAdapter: QSSceneAdapter,
+ modifier: Modifier = Modifier,
+) {
+ val isShowing by viewModel.isShowing.collectAsState()
+ val mirrorAlpha by
+ animateFloatAsState(
+ targetValue = if (isShowing) 1f else 0f,
+ label = "alphaAnimationBrightnessMirrorShowing",
+ )
+ val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+ val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
+
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
+ QuickSettingsTheme {
+ // The assumption for using this AndroidView is that there will be only one in view at
+ // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually
+ // `BrightnessSliderController` only supports a single mirror).
+ // The benefit of doing it like this is that if the configuration changes or QSImpl is
+ // re-inflated, it's not relevant to the composable, as we'll always get a new one.
+ AndroidView(
+ modifier =
+ Modifier.align(Alignment.TopCenter)
+ .offset { offset }
+ .width { mirrorOffsetAndSize.width }
+ .height { mirrorOffsetAndSize.height },
+ factory = { context ->
+ val (view, controller) =
+ BrightnessMirrorInflater.inflate(context, viewModel.sliderControllerFactory)
+ viewModel.setToggleSlider(controller)
+ view
+ },
+ update = { qsSceneAdapter.setBrightnessMirrorController(viewModel) },
+ onRelease = { qsSceneAdapter.setBrightnessMirrorController(null) }
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 5b9213a8f23c..a3768346e7c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -17,7 +17,9 @@
package com.android.systemui.qs.ui.composable
import android.view.ViewGroup
+import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
@@ -48,6 +50,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -119,9 +122,28 @@ private fun SceneScope.QuickSettingsScene(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter
+ )
+
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier.fillMaxSize()) {
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+
+ BackHandler(
+ enabled = isCustomizing,
+ ) {
+ viewModel.qsSceneAdapter.requestCloseCustomizer()
+ }
+
val collapsedHeaderHeight =
with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
val lifecycleOwner = LocalLifecycleOwner.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index fcd77609768e..02a12e4e0814 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -54,9 +54,9 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
-import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.settingslib.Utils
import com.android.systemui.battery.BatteryMeterView
@@ -87,10 +87,6 @@ object ShadeHeader {
val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
}
- object Keys {
- val transitionProgress = ValueKey("ShadeHeaderTransitionProgress")
- }
-
object Values {
val ClockScale = ValueKey("ShadeHeaderClockScale")
}
@@ -119,19 +115,17 @@ fun SceneScope.CollapsedShadeHeader(
return
}
- val formatProgress =
- animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 0f)
-
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutLocation = LocalDisplayCutout.current.location
val useExpandedFormat by
- remember(formatProgress) {
+ remember(cutoutLocation) {
derivedStateOf {
- cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+ cutoutLocation != CutoutLocation.CENTER ||
+ shouldUseExpandedFormat(layoutState.transitionState)
}
}
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
// This layout assumes it is globally positioned at (0, 0) and is the
@@ -207,7 +201,7 @@ fun SceneScope.CollapsedShadeHeader(
val screenWidth = constraints.maxWidth
val cutoutWidthPx = cutoutWidth.roundToPx()
- val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val height = CollapsedHeight.roundToPx()
val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
val startMeasurable = measurables[0][0]
@@ -261,11 +255,10 @@ fun SceneScope.ExpandedShadeHeader(
return
}
- val formatProgress =
- animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 1f)
- val useExpandedFormat by
- remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+ val useExpandedFormat by remember {
+ derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
+ }
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
Box(modifier = modifier) {
@@ -530,3 +523,15 @@ private fun SceneScope.PrivacyChip(
modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
)
}
+
+private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
+ return when (state) {
+ is TransitionState.Idle -> {
+ state.currentScene == Scenes.QuickSettings
+ }
+ is TransitionState.Transition -> {
+ (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) ||
+ (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 85798acd0dcd..9bd6f817cff3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -33,6 +34,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -45,6 +47,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
@@ -70,6 +73,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
+import com.android.systemui.qs.ui.composable.BrightnessMirror
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -302,12 +306,25 @@ private fun SceneScope.SplitShade(
}
}
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
+
Box(
modifier =
modifier
.fillMaxSize()
.element(Shade.Elements.BackgroundScrim)
- .background(colorResource(R.color.shade_scrim_background_dark))
+ // Cannot set the alpha of the whole element to 0, because the mirror should be
+ // in the QS column.
+ .background(
+ colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
+ )
) {
Column(
modifier = Modifier.fillMaxSize(),
@@ -317,61 +334,80 @@ private fun SceneScope.SplitShade(
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ modifier =
+ Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ .then(brightnessMirrorShowingModifier)
)
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- Column(
- verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.weight(1f).fillMaxSize().thenIf(!isCustomizing) {
- Modifier.padding(bottom = navBarBottomHeight)
- },
- ) {
+ Box(modifier = Modifier.weight(1f)) {
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ // Need to remove the offset of the header height, as the mirror uses
+ // the position of the Brightness slider in the window
+ modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight)
+ )
Column(
+ verticalArrangement = Arrangement.Top,
modifier =
- Modifier.fillMaxSize().weight(1f).thenIf(!isCustomizing) {
- Modifier.verticalNestedScrollToScene()
- .verticalScroll(
- quickSettingsScrollState,
- enabled = isScrollable
- )
- .clipScrollableContainer(Orientation.Horizontal)
- }
+ Modifier.fillMaxSize().thenIf(!isCustomizing) {
+ Modifier.padding(bottom = navBarBottomHeight)
+ },
) {
- Box(
+ Column(
modifier =
- Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ Modifier.fillMaxSize()
+ .weight(1f)
+ .thenIf(!isCustomizing) {
+ Modifier.verticalNestedScrollToScene()
+ .verticalScroll(
+ quickSettingsScrollState,
+ enabled = isScrollable
+ )
+ .clipScrollableContainer(Orientation.Horizontal)
+ }
+ .then(brightnessMirrorShowingModifier)
) {
- QuickSettings(
- qsSceneAdapter = viewModel.qsSceneAdapter,
- heightProvider = { viewModel.qsSceneAdapter.qsHeight },
- isSplitShade = true,
+ Box(
+ modifier =
+ Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ squishiness = tileSquishiness,
+ )
+ }
+
+ MediaIfVisible(
+ viewModel = viewModel,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
- squishiness = tileSquishiness,
)
}
-
- MediaIfVisible(
- viewModel = viewModel,
- mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
+ FooterActionsWithAnimatedVisibility(
+ viewModel = footerActionsViewModel,
+ isCustomizing = isCustomizing,
+ lifecycleOwner = lifecycleOwner,
+ modifier =
+ Modifier.align(Alignment.CenterHorizontally)
+ .then(brightnessMirrorShowingModifier),
)
}
- FooterActionsWithAnimatedVisibility(
- viewModel = footerActionsViewModel,
- isCustomizing = isCustomizing,
- lifecycleOwner = lifecycleOwner,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
}
NotificationScrollingStack(
viewModel = viewModel.notifications,
maxScrimTop = { 0f },
modifier =
- Modifier.weight(1f).fillMaxHeight().padding(bottom = navBarBottomHeight),
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .padding(bottom = navBarBottomHeight)
+ .then(brightnessMirrorShowingModifier),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index b1fbe35eccd8..9f0da004730d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable
import android.content.Context
import android.view.ContextThemeWrapper
import android.view.View
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -55,6 +56,7 @@ constructor(
@Composable
private fun Title() {
Text(
+ modifier = Modifier.basicMarquee(),
text = stringResource(R.string.volume_panel_noise_control_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
new file mode 100644
index 000000000000..167eb65da7ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Container to create a rim around the button. Both `Expandable` and `OutlinedIconToggleButton`
+ * have antialiasing problem when used with [androidx.compose.foundation.BorderStroke] and some
+ * contrast container color.
+ */
+// TODO(b/331584069): Remove this once antialiasing bug is fixed
+@Composable
+fun BottomComponentButtonSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+ Surface(
+ modifier = modifier.height(64.dp),
+ shape = RoundedCornerShape(28.dp),
+ color = MaterialTheme.colorScheme.surface,
+ content = content,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index b721e41eda27..fc511e12ec54 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,13 +16,12 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -37,7 +36,6 @@ import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.animation.Expandable
@@ -64,28 +62,28 @@ class ButtonComponent(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- Expandable(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics {
- role = Role.Button
- contentDescription = label
- },
- color = MaterialTheme.colorScheme.primaryContainer,
- shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- onClick = onClick,
- ) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ Expandable(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Button
+ contentDescription = label
+ },
+ color = MaterialTheme.colorScheme.primaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ onClick = onClick,
+ ) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
}
Text(
- modifier = Modifier.clearAndSetSemantics {},
+ modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 28fd785a09e5..780e3f2de4c8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -16,26 +16,28 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
@@ -60,29 +62,38 @@ class ToggleButtonComponent(
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- OutlinedIconToggleButton(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
- checked = viewModel.isChecked,
- onCheckedChange = onCheckedChange,
- shape = RoundedCornerShape(28.dp),
- colors =
- IconButtonDefaults.outlinedIconToggleButtonColors(
- containerColor = MaterialTheme.colorScheme.surface,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
- checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- ),
- border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- ) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ val colors =
+ if (viewModel.isChecked) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ Button(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Switch
+ contentDescription = label
+ },
+ onClick = { onCheckedChange(!viewModel.isChecked) },
+ shape = RoundedCornerShape(28.dp),
+ colors = colors
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
+
Text(
- modifier = Modifier.clearAndSetSemantics {},
+ modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 26086d1a9d0a..9f9bc623a6b3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -33,6 +33,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -82,7 +84,8 @@ constructor(
title: @Composable (SystemUIDialog) -> Unit,
content: @Composable (SystemUIDialog) -> Unit,
) {
- Box(Modifier.fillMaxWidth()) {
+ val paneTitle = stringResource(R.string.accessibility_volume_settings)
+ Box(Modifier.fillMaxWidth().semantics(properties = { this.paneTitle = paneTitle })) {
Column(
modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index 98d1afd7af7e..c74331477229 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -19,10 +19,13 @@ package com.android.systemui.volume.panel.component.selector.ui.composable
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
@@ -32,6 +35,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
@@ -42,6 +46,12 @@ import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -108,11 +118,24 @@ fun VolumePanelRadioButtonBar(
horizontalArrangement = Arrangement.spacedBy(spacing)
) {
for (itemIndex in items.indices) {
- TextButton(
- modifier = Modifier.weight(1f),
- onClick = { items[itemIndex].onItemSelected() },
+ val item = items[itemIndex]
+ Row(
+ modifier =
+ Modifier.height(48.dp)
+ .weight(1f)
+ .semantics {
+ item.contentDescription?.let { contentDescription = it }
+ role = Role.Switch
+ selected = itemIndex == scope.selectedIndex
+ }
+ .clickable(
+ interactionSource = null,
+ indication = null,
+ onClick = { items[itemIndex].onItemSelected() }
+ ),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically,
) {
- val item = items[itemIndex]
if (item.icon !== Empty) {
with(items[itemIndex]) { icon() }
}
@@ -126,13 +149,17 @@ fun VolumePanelRadioButtonBar(
start = indicatorBackgroundPadding,
top = labelIndicatorBackgroundSpacing,
end = indicatorBackgroundPadding
- ),
+ )
+ .clearAndSetSemantics {},
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
for (itemIndex in items.indices) {
+ val cornersRadius = 4.dp
TextButton(
modifier = Modifier.weight(1f),
onClick = { items[itemIndex].onItemSelected() },
+ shape = RoundedCornerShape(cornersRadius),
+ contentPadding = PaddingValues(cornersRadius)
) {
val item = items[itemIndex]
if (item.icon !== Empty) {
@@ -246,7 +273,7 @@ object VolumePanelRadioButtonBarDefaults {
val DefaultSpacing = 24.dp
val DefaultLabelIndicatorBackgroundSpacing = 12.dp
val DefaultIndicatorCornerRadius = 20.dp
- val DefaultIndicatorBackgroundCornerRadius = 20.dp
+ val DefaultIndicatorBackgroundCornerRadius = 28.dp
/**
* Returns the default VolumePanelRadioButtonBar colors.
@@ -281,6 +308,7 @@ interface VolumePanelRadioButtonBarScope {
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit = Empty,
label: @Composable RowScope.() -> Unit = Empty,
+ contentDescription: String? = null,
)
}
@@ -302,6 +330,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit,
label: @Composable RowScope.() -> Unit,
+ contentDescription: String?,
) {
require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
if (isSelected) {
@@ -312,6 +341,7 @@ private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScop
onItemSelected = onItemSelected,
icon = icon,
label = label,
+ contentDescription = contentDescription,
)
)
}
@@ -325,6 +355,7 @@ private class Item(
val onItemSelected: () -> Unit,
val icon: @Composable RowScope.() -> Unit,
val label: @Composable RowScope.() -> Unit,
+ val contentDescription: String?,
)
private const val UNSET_OFFSET = -1
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 71b3e8a6a102..eed54dab6faf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -16,12 +16,14 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+import androidx.compose.foundation.basicMarquee
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import com.android.systemui.animation.Expandable
@@ -49,6 +51,7 @@ constructor(
@Composable
private fun Title() {
Text(
+ modifier = Modifier.basicMarquee(),
text = stringResource(R.string.volume_panel_spatial_audio_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
@@ -71,9 +74,11 @@ constructor(
}
VolumePanelRadioButtonBar {
for (buttonViewModel in enabledModelStates) {
+ val label = buttonViewModel.button.label.toString()
item(
isSelected = buttonViewModel.button.isChecked,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+ contentDescription = label,
icon = {
Icon(
icon = buttonViewModel.button.icon,
@@ -82,9 +87,12 @@ constructor(
},
label = {
Text(
- text = buttonViewModel.button.label.toString(),
+ modifier = Modifier.basicMarquee(),
+ text = label,
style = MaterialTheme.typography.labelMedium,
color = buttonViewModel.labelColor.toColor(),
+ textAlign = TextAlign.Center,
+ maxLines = 2
)
}
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index e1cf3a5f373b..f89669c8456c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -28,9 +28,8 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@@ -57,6 +56,8 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
private const val EXPAND_DURATION_MILLIS = 500
private const val COLLAPSE_DURATION_MILLIS = 300
+private const val SHRINK_FRACTION = 0.55f
+private const val SCALE_FRACTION = 0.9f
/** Volume sliders laid out in a collapsable column */
@OptIn(ExperimentalAnimationApi::class)
@@ -107,34 +108,33 @@ fun ColumnVolumeSliders(
transition.AnimatedVisibility(
visible = { it },
enter =
- expandVertically(
- animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS),
- expandFrom = Alignment.CenterVertically,
- ),
+ expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
exit =
- shrinkVertically(
- animationSpec = tween(durationMillis = COLLAPSE_DURATION_MILLIS),
- shrinkTowards = Alignment.CenterVertically,
- ),
+ shrinkVertically(animationSpec = tween(durationMillis = COLLAPSE_DURATION_MILLIS)),
) {
- Column(modifier = Modifier.fillMaxWidth()) {
- for (index in 1..viewModels.lastIndex) {
- val sliderViewModel: SliderViewModel = viewModels[index]
- val sliderState by sliderViewModel.slider.collectAsState()
- transition.AnimatedVisibility(
- visible = { it },
- enter = enterTransition(index = index, totalCount = viewModels.size),
- exit = exitTransition(index = index, totalCount = viewModels.size)
- ) {
- VolumeSlider(
- modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
- state = sliderState,
- onValueChange = { newValue: Float ->
- sliderViewModel.onValueChanged(sliderState, newValue)
- },
- onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
- sliderColors = sliderColors,
- )
+ // This box allows sliders to slide towards top when the container is shrinking and
+ // slide from top when the container is expanding.
+ Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomCenter) {
+ Column {
+ for (index in 1..viewModels.lastIndex) {
+ val sliderViewModel: SliderViewModel = viewModels[index]
+ val sliderState by sliderViewModel.slider.collectAsState()
+ transition.AnimatedVisibility(
+ modifier = Modifier.padding(top = 16.dp),
+ visible = { it },
+ enter = enterTransition(index = index, totalCount = viewModels.size),
+ exit = exitTransition(index = index, totalCount = viewModels.size)
+ ) {
+ VolumeSlider(
+ modifier = Modifier.fillMaxWidth(),
+ state = sliderState,
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
+ sliderColors = sliderColors,
+ )
+ }
}
}
}
@@ -175,19 +175,14 @@ private fun ExpandButton(
private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
- return slideInVertically(
- initialOffsetY = { (it * 0.25).toInt() },
+ return scaleIn(
+ initialScale = SCALE_FRACTION,
animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
) +
- scaleIn(
- initialScale = 0.9f,
- animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
- ) +
expandVertically(
- initialHeight = { (it * 0.65).toInt() },
+ initialHeight = { (it * SHRINK_FRACTION).toInt() },
animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
clip = false,
- expandFrom = Alignment.CenterVertically,
) +
fadeIn(
animationSpec = tween(durationMillis = enterDuration, delayMillis = enterDelay),
@@ -196,19 +191,14 @@ private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
private fun exitTransition(index: Int, totalCount: Int): ExitTransition {
val exitDuration = (COLLAPSE_DURATION_MILLIS - (totalCount - index + 1) * 10).coerceAtLeast(100)
- return slideOutVertically(
- targetOffsetY = { (it * 0.25).toInt() },
+ return scaleOut(
+ targetScale = SCALE_FRACTION,
animationSpec = tween(durationMillis = exitDuration),
) +
- scaleOut(
- targetScale = 0.9f,
- animationSpec = tween(durationMillis = exitDuration),
- ) +
shrinkVertically(
- targetHeight = { (it * 0.65).toInt() },
+ targetHeight = { (it * SHRINK_FRACTION).toInt() },
animationSpec = tween(durationMillis = exitDuration),
clip = false,
- shrinkTowards = Alignment.CenterVertically,
) +
fadeOut(animationSpec = tween(durationMillis = exitDuration))
}
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 d31064ae23b3..19d3f599ef31 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
@@ -17,10 +17,10 @@
package com.android.systemui.volume.panel.component.volume.ui.composable
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -32,7 +32,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
@@ -130,24 +129,20 @@ private fun SliderIcon(
isTappable: Boolean,
modifier: Modifier = Modifier
) {
- if (isTappable) {
- IconButton(
- modifier = modifier,
- onClick = onIconTapped,
- colors =
- IconButtonColors(
- contentColor = LocalContentColor.current,
- containerColor = Color.Transparent,
- disabledContentColor = LocalContentColor.current,
- disabledContainerColor = Color.Transparent,
- ),
- content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
- )
- } else {
- Box(
- modifier = modifier,
- contentAlignment = Alignment.Center,
- content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
- )
- }
+ val boxModifier =
+ if (isTappable) {
+ modifier.clickable(
+ onClick = onIconTapped,
+ interactionSource = null,
+ indication = null
+ )
+ } else {
+ modifier
+ }
+ .fillMaxSize()
+ Box(
+ modifier = boxModifier,
+ contentAlignment = Alignment.Center,
+ content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
+ )
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index dd767817a5ae..9ea20b9da4b6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -20,6 +20,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
@@ -27,6 +28,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastSumBy
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@Composable
@@ -53,6 +55,14 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
) {
+ val visibleComponentsCount =
+ layout.footerComponents.fastSumBy { if (it.isVisible) 1 else 0 }
+
+ // Center footer component if there is only one present
+ if (visibleComponentsCount == 1) {
+ Spacer(modifier = Modifier.weight(0.5f))
+ }
+
for (component in layout.footerComponents) {
AnimatedVisibility(
visible = component.isVisible,
@@ -63,6 +73,10 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent(
}
}
}
+
+ if (visibleComponentsCount == 1) {
+ Spacer(modifier = Modifier.weight(0.5f))
+ }
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e7cb5a45906d..a8a1d881b907 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -261,16 +261,19 @@ private fun shouldDrawElement(
scene: Scene,
element: Element,
): Boolean {
- val transition = layoutImpl.state.currentTransition
-
- // Always draw the element if there is no ongoing transition or if the element is not shared or
- // if the current scene is the one that is currently over scrolling with [OverscrollSpec].
- if (
- transition == null ||
- transition.fromScene !in element.sceneStates ||
- transition.toScene !in element.sceneStates ||
- transition.currentOverscrollSpec?.scene == scene.key
- ) {
+ val transition = layoutImpl.state.currentTransition ?: return true
+
+ val inFromScene = transition.fromScene in element.sceneStates
+ val inToScene = transition.toScene in element.sceneStates
+
+ // If an element is not present in any scene, it should not be drawn.
+ if (!inFromScene && !inToScene) {
+ return false
+ }
+
+ // Always draw if the element is not shared or if the current scene is the one that is currently
+ // over scrolling with [OverscrollSpec].
+ if (!inFromScene || !inToScene || transition.currentOverscrollSpec?.scene == scene.key) {
return true
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 2453e251b5a4..458a2b99f9ce 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -45,6 +45,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
@@ -175,6 +176,60 @@ class ElementTest {
}
@Test
+ fun elementsNotInTransition_shouldNotBeDrawn() {
+ val nFrames = 20
+ val frameDuration = 16L
+ val tween = tween<Float>(nFrames * frameDuration.toInt())
+ val layoutSize = 100.dp
+ val elementSize = 50.dp
+ val elementOffset = 20.dp
+
+ lateinit var changeScene: (SceneKey) -> Unit
+
+ rule.testTransition(
+ from = TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ transitionLayout = { currentScene, onChangeScene ->
+ changeScene = onChangeScene
+
+ SceneTransitionLayout(
+ currentScene,
+ onChangeScene,
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween }
+ from(TestScenes.SceneB, to = TestScenes.SceneC) { spec = tween }
+ },
+ ) {
+ scene(TestScenes.SceneA) {
+ Box(Modifier.size(layoutSize)) {
+ // Transformed element
+ Element(
+ TestElements.Bar,
+ elementSize,
+ elementOffset,
+ )
+ }
+ }
+ scene(TestScenes.SceneB) { Box(Modifier.size(layoutSize)) }
+ scene(TestScenes.SceneC) { Box(Modifier.size(layoutSize)) }
+ }
+ },
+ ) {
+ // Start transition from SceneA to SceneB
+ at(1 * frameDuration) {
+ onElement(TestElements.Bar).assertExists()
+
+ // Start transition from SceneB to SceneC
+ changeScene(TestScenes.SceneC)
+ }
+
+ at(2 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
+
+ at(3 * frameDuration) { onElement(TestElements.Bar).assertDoesNotExist() }
+ }
+ }
+
+ @Test
fun onlyMovingElements_noLayout_onlyPlacement() {
val nFrames = 20
val layoutSize = 100.dp
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9dbeeda42986..1120914fec7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,15 +47,16 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
@@ -832,7 +833,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Gone, "reason")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index cb8cebf80767..81878aaf4a18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -179,10 +179,14 @@ class AuthenticationInteractorTest : SysuiTestCase() {
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- val shorterPin =
- FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() }
+ val defaultPin = FakeAuthenticationRepository.DEFAULT_PIN.toMutableList()
- assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
+ assertSkipped(
+ underTest.authenticate(
+ defaultPin.subList(0, defaultPin.size - 1),
+ tryAutoConfirm = true
+ )
+ )
assertThat(underTest.lockoutEndTimestamp).isNull()
assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@@ -201,7 +205,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertFailed(
underTest.authenticate(wrongPin, tryAutoConfirm = true),
- assertNoResultEvents = true,
)
}
@@ -219,7 +222,6 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertFailed(
underTest.authenticate(longerPin, tryAutoConfirm = true),
- assertNoResultEvents = true,
)
}
@@ -547,14 +549,9 @@ class AuthenticationInteractorTest : SysuiTestCase() {
private fun assertFailed(
authenticationResult: AuthenticationResult,
- assertNoResultEvents: Boolean = false,
) {
assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
- if (assertNoResultEvents) {
- assertThat(onAuthenticationResult).isNull()
- } else {
- assertThat(onAuthenticationResult).isFalse()
- }
+ assertThat(onAuthenticationResult).isFalse()
}
private fun assertSkipped(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index 16ec9aa897fb..f9f7df820217 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -122,7 +122,6 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
bouncerInteractor.authenticate(WRONG_PIN)
}
- assertThat(message?.isUpdateAnimated).isFalse()
val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
advanceTimeBy(lockoutEndMs - testScope.currentTime)
@@ -133,6 +132,7 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
fun lockoutMessage() =
testScope.runTest {
val message by collectLastValue(underTest.message)
+ val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
runCurrent()
@@ -140,14 +140,14 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
bouncerInteractor.authenticate(WRONG_PIN)
runCurrent()
- if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ if (times == FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+ assertTryAgainMessage(message?.text, lockoutSeconds)
+ assertThat(message?.isUpdateAnimated).isFalse()
+ } else {
assertThat(message?.text).isEqualTo("Wrong PIN. Try again.")
assertThat(message?.isUpdateAnimated).isTrue()
}
}
- val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
- assertTryAgainMessage(message?.text, lockoutSeconds)
- assertThat(message?.isUpdateAnimated).isFalse()
repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time ->
advanceTimeBy(1.seconds)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 71c578545647..c28cf348b9fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.inputmethod.data.model.InputMethodModel
import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
@@ -153,7 +152,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
// No input entered.
@@ -329,7 +327,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 51b73ee92df5..14d36343041d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -373,7 +372,6 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 564795429fa6..5bb36a0acbdf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -253,7 +252,14 @@ class PinBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
+ // TODO(b/332768183) remove this after the bug if fixed.
+ // Collect the flow so that it is hot, in the real application this is done by using a
+ // refreshingFlow that relies on the UI to make this flow hot.
+ val autoConfirmEnabled by
+ collectLastValue(authenticationInteractor.isAutoConfirmEnabled)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+ assertThat(autoConfirmEnabled).isTrue()
val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
@@ -265,9 +271,17 @@ class PinBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
+ // TODO(b/332768183) remove this after the bug if fixed.
+ // Collect the flow so that it is hot, in the real application this is done by using a
+ // refreshingFlow that relies on the UI to make this flow hot.
+ val autoConfirmEnabled by
+ collectLastValue(authenticationInteractor.isAutoConfirmEnabled)
+
val currentScene by collectLastValue(sceneInteractor.currentScene)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+ assertThat(autoConfirmEnabled).isTrue()
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -387,7 +401,6 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private fun TestScope.lockDeviceAndOpenPinBouncer() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
new file mode 100644
index 000000000000..312c14df9505
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.data.repository
+
+import android.content.applicationContext
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.PolicyRestriction
+import com.android.systemui.utils.UserRestrictionChecker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val fakeUserRepository = kosmos.fakeUserRepository
+
+ private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
+ whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
+ whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
+ }
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessPolicyRepositoryImpl(
+ userRepository,
+ mockUserRestrictionChecker,
+ applicationContext,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun noRestrictionByDefaultForAllUsers() =
+ with(kosmos) {
+ testScope.runTest {
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+
+ fakeUserRepository.asMainUser()
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+ }
+ }
+
+ @Test
+ fun restrictDefaultUser() =
+ with(kosmos) {
+ testScope.runTest {
+ val enforcedAdmin: EnforcedAdmin =
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+ whenever(
+ mockUserRestrictionChecker.checkIfRestrictionEnforced(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(enforcedAdmin)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+
+ fakeUserRepository.asMainUser()
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+ }
+ }
+
+ @Test
+ fun restrictMainUser() =
+ with(kosmos) {
+ testScope.runTest {
+ val enforcedAdmin: EnforcedAdmin =
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+ whenever(
+ mockUserRestrictionChecker.checkIfRestrictionEnforced(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.mainUserId)
+ )
+ )
+ .thenReturn(enforcedAdmin)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+
+ fakeUserRepository.asMainUser()
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+ }
+ }
+
+ private companion object {
+ val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
new file mode 100644
index 000000000000..e39ad4f0b405
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.data.repository
+
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
+import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() {
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ private val kosmos = testKosmos()
+
+ private var currentBrightnessInfo = BrightnessInfo()
+
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var display: Display
+
+ private val displayId = 0
+
+ private lateinit var underTest: ScreenBrightnessDisplayManagerRepository
+
+ @Before
+ fun setUp() {
+ underTest =
+ ScreenBrightnessDisplayManagerRepository(
+ displayId,
+ displayManager,
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ )
+
+ whenever(displayManager.getDisplay(displayId)).thenReturn(display)
+ // Using then answer so it will be retrieved in every call
+ whenever(display.brightnessInfo).thenAnswer { currentBrightnessInfo }
+ }
+
+ @Test
+ fun startingBrightnessInfo() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness by collectLastValue(underTest.linearBrightness)
+ val minBrightness by collectLastValue(underTest.minLinearBrightness)
+ val maxBrightness by collectLastValue(underTest.maxLinearBrightness)
+ runCurrent()
+
+ assertThat(brightness?.floatValue).isEqualTo(currentBrightnessInfo.brightness)
+ assertThat(minBrightness?.floatValue)
+ .isEqualTo(currentBrightnessInfo.brightnessMinimum)
+ assertThat(maxBrightness?.floatValue)
+ .isEqualTo(currentBrightnessInfo.brightnessMaximum)
+ }
+ }
+
+ @Test
+ fun followsChangingBrightnessInfo() =
+ with(kosmos) {
+ testScope.runTest {
+ val listenerCaptor = argumentCaptor<DisplayManager.DisplayListener>()
+
+ val brightness by collectLastValue(underTest.linearBrightness)
+ val minBrightness by collectLastValue(underTest.minLinearBrightness)
+ val maxBrightness by collectLastValue(underTest.maxLinearBrightness)
+ runCurrent()
+
+ verify(displayManager)
+ .registerDisplayListener(
+ capture(listenerCaptor),
+ eq(null),
+ eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+ )
+
+ val newBrightness = BrightnessInfo(0.6f, 0.3f, 0.9f)
+ changeBrightnessInfoAndNotify(newBrightness, listenerCaptor.value)
+
+ assertThat(brightness?.floatValue).isEqualTo(currentBrightnessInfo.brightness)
+ assertThat(minBrightness?.floatValue)
+ .isEqualTo(currentBrightnessInfo.brightnessMinimum)
+ assertThat(maxBrightness?.floatValue)
+ .isEqualTo(currentBrightnessInfo.brightnessMaximum)
+ }
+ }
+
+ @Test
+ fun minMaxWhenNotCollecting() =
+ with(kosmos) {
+ testScope.runTest {
+ currentBrightnessInfo = BrightnessInfo(0.5f, 0.1f, 0.7f)
+ val (min, max) = underTest.getMinMaxLinearBrightness()
+ assertThat(min.floatValue).isEqualTo(currentBrightnessInfo.brightnessMinimum)
+ assertThat(max.floatValue).isEqualTo(currentBrightnessInfo.brightnessMaximum)
+ }
+ }
+
+ @Test
+ fun minMaxWhenCollecting() =
+ with(kosmos) {
+ testScope.runTest {
+ val listenerCaptor = argumentCaptor<DisplayManager.DisplayListener>()
+
+ val brightness by collectLastValue(underTest.linearBrightness)
+ runCurrent()
+
+ verify(displayManager)
+ .registerDisplayListener(
+ capture(listenerCaptor),
+ eq(null),
+ eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+ )
+
+ changeBrightnessInfoAndNotify(
+ BrightnessInfo(0.5f, 0.1f, 0.7f),
+ listenerCaptor.value
+ )
+ runCurrent()
+
+ val (min, max) = underTest.getMinMaxLinearBrightness()
+ assertThat(min.floatValue).isEqualTo(currentBrightnessInfo.brightnessMinimum)
+ assertThat(max.floatValue).isEqualTo(currentBrightnessInfo.brightnessMaximum)
+ }
+ }
+
+ @Test
+ fun setTemporaryBrightness_insideBounds() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness = 0.3f
+ underTest.setTemporaryBrightness(LinearBrightness(brightness))
+ runCurrent()
+
+ verify(displayManager).setTemporaryBrightness(displayId, brightness)
+ verify(displayManager, never()).setBrightness(anyInt(), anyFloat())
+ }
+ }
+
+ @Test
+ fun setTemporaryBrightness_outsideBounds() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness = 1.3f
+ underTest.setTemporaryBrightness(LinearBrightness(brightness))
+ runCurrent()
+
+ verify(displayManager)
+ .setTemporaryBrightness(displayId, currentBrightnessInfo.brightnessMaximum)
+ verify(displayManager, never()).setBrightness(anyInt(), anyFloat())
+ }
+ }
+
+ @Test
+ fun setBrightness_insideBounds() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness = 0.3f
+ underTest.setBrightness(LinearBrightness(brightness))
+ runCurrent()
+
+ verify(displayManager).setBrightness(displayId, brightness)
+ verify(displayManager, never()).setTemporaryBrightness(anyInt(), anyFloat())
+ }
+ }
+
+ @Test
+ fun setBrightness_outsideBounds() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness = 1.3f
+ underTest.setBrightness(LinearBrightness(brightness))
+ runCurrent()
+
+ verify(displayManager)
+ .setBrightness(displayId, currentBrightnessInfo.brightnessMaximum)
+ verify(displayManager, never()).setTemporaryBrightness(anyInt(), anyFloat())
+ }
+ }
+
+ private fun changeBrightnessInfoAndNotify(
+ newValue: BrightnessInfo,
+ listener: DisplayManager.DisplayListener,
+ ) {
+ currentBrightnessInfo = newValue
+ listener.onDisplayChanged(displayId)
+ }
+
+ companion object {
+ fun BrightnessInfo(
+ brightness: Float = 0f,
+ minBrightness: Float = 0f,
+ maxBrightness: Float = 1f,
+ ): BrightnessInfo {
+ return BrightnessInfo(
+ brightness,
+ minBrightness,
+ maxBrightness,
+ HIGH_BRIGHTNESS_MODE_OFF,
+ 1f,
+ BRIGHTNESS_MAX_REASON_NONE,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
new file mode 100644
index 000000000000..85a4bcf62223
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.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.brightness.domain.interactor
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.brightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.fakeBrightnessPolicyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.utils.PolicyRestriction
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val mockActivityStarter = kosmos.activityStarter
+ private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessPolicyEnforcementInteractor(
+ brightnessPolicyRepository,
+ activityStarter,
+ )
+ }
+
+ @Test
+ fun restriction() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeBrightnessPolicyRepository.setCurrentUserUnrestricted()
+
+ val restriction by collectLastValue(underTest.brightnessPolicyRestriction)
+
+ assertThat(restriction).isEqualTo(PolicyRestriction.NoRestriction)
+
+ fakeBrightnessPolicyRepository.setCurrentUserRestricted()
+
+ assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+ }
+ }
+
+ @Test
+ fun startRestrictionDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ val enforcedAdmin =
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+ BrightnessPolicyRepository.RESTRICTION
+ )
+ .apply {
+ component = TEST_COMPONENT
+ user = UserHandle.of(TEST_USER)
+ }
+
+ underTest.startAdminSupportDetailsDialog(
+ PolicyRestriction.Restricted(enforcedAdmin)
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+
+ verify(mockActivityStarter)
+ .postStartActivityDismissingKeyguard(
+ capture(intentCaptor),
+ eq(0),
+ )
+
+ val expectedIntent =
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(enforcedAdmin)
+
+ with(intentCaptor.value) {
+ assertThat(action).isEqualTo(expectedIntent.action)
+ assertThat(extras!!.kindofEquals(expectedIntent.extras)).isTrue()
+ }
+ }
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("pkg", ".cls")
+ val TEST_USER = 10
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
new file mode 100644
index 000000000000..33c44f8a331e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
+import com.android.systemui.brightness.data.repository.screenBrightnessRepository
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenBrightnessInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository)
+
+ @Test
+ fun gammaBrightness() =
+ with(kosmos) {
+ testScope.runTest {
+ val gammaBrightness by collectLastValue(underTest.gammaBrightness)
+
+ val brightness = 0.3f
+ val min = 0f
+ val max = 1f
+
+ with(fakeScreenBrightnessRepository) {
+ setBrightness(LinearBrightness(brightness))
+ setMinMaxBrightness(LinearBrightness(min), LinearBrightness(max))
+ }
+ runCurrent()
+
+ assertThat(gammaBrightness?.value)
+ .isEqualTo(BrightnessUtils.convertLinearToGammaFloat(brightness, min, max))
+ }
+ }
+
+ @Test
+ fun gammaBrightness_constrained() =
+ with(kosmos) {
+ testScope.runTest {
+ val gammaBrightness by collectLastValue(underTest.gammaBrightness)
+
+ val brightness = 0.3f
+ val min = 0.2f
+ val max = 0.8f
+
+ with(fakeScreenBrightnessRepository) {
+ setBrightness(LinearBrightness(brightness))
+ setMinMaxBrightness(LinearBrightness(min), LinearBrightness(max))
+ }
+ runCurrent()
+
+ assertThat(gammaBrightness?.value)
+ .isEqualTo(BrightnessUtils.convertLinearToGammaFloat(brightness, min, max))
+ }
+ }
+
+ @Test
+ fun setTemporaryBrightness() =
+ with(kosmos) {
+ testScope.runTest {
+ val temporaryBrightness by
+ collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
+ val brightness by collectLastValue(underTest.gammaBrightness)
+
+ val gammaBrightness = 30000
+ underTest.setTemporaryBrightness(GammaBrightness(gammaBrightness))
+
+ val (min, max) = fakeScreenBrightnessRepository.getMinMaxLinearBrightness()
+
+ val expectedTemporaryBrightness =
+ BrightnessUtils.convertGammaToLinearFloat(
+ gammaBrightness,
+ min.floatValue,
+ max.floatValue
+ )
+ assertThat(temporaryBrightness!!.floatValue)
+ .isWithin(1e-5f)
+ .of(expectedTemporaryBrightness)
+ assertThat(brightness!!.value).isNotEqualTo(gammaBrightness)
+ }
+ }
+
+ @Test
+ fun setBrightness() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness by collectLastValue(fakeScreenBrightnessRepository.linearBrightness)
+
+ val gammaBrightness = 30000
+ underTest.setBrightness(GammaBrightness(gammaBrightness))
+
+ val (min, max) = fakeScreenBrightnessRepository.getMinMaxLinearBrightness()
+
+ val expectedBrightness =
+ BrightnessUtils.convertGammaToLinearFloat(
+ gammaBrightness,
+ min.floatValue,
+ max.floatValue
+ )
+ assertThat(brightness!!.floatValue).isWithin(1e-5f).of(expectedBrightness)
+ }
+ }
+
+ @Test
+ fun maxGammaBrightness() {
+ assertThat(underTest.maxGammaBrightness)
+ .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX))
+ }
+
+ @Test
+ fun minGammaBrightness() {
+ assertThat(underTest.minGammaBrightness)
+ .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
new file mode 100644
index 000000000000..0058ee4a9c4e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
+import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
+import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessSliderViewModelTest : SysuiTestCase() {
+
+ private val minBrightness = 0f
+ private val maxBrightness = 1f
+
+ private val kosmos = testKosmos()
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessSliderViewModel(
+ screenBrightnessInteractor,
+ brightnessPolicyEnforcementInteractor,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.fakeScreenBrightnessRepository.setMinMaxBrightness(
+ LinearBrightness(minBrightness),
+ LinearBrightness(maxBrightness)
+ )
+ }
+
+ @Test
+ fun brightnessChangeInRepository_changeInFlow() =
+ with(kosmos) {
+ testScope.runTest {
+ val gammaBrightness by collectLastValue(underTest.currentBrightness)
+
+ var brightness = 0.6f
+ fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+
+ assertThat(gammaBrightness!!.value)
+ .isEqualTo(
+ BrightnessUtils.convertLinearToGammaFloat(
+ brightness,
+ minBrightness,
+ maxBrightness
+ )
+ )
+
+ brightness = 0.2f
+ fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+
+ assertThat(gammaBrightness!!.value)
+ .isEqualTo(
+ BrightnessUtils.convertLinearToGammaFloat(
+ brightness,
+ minBrightness,
+ maxBrightness
+ )
+ )
+ }
+ }
+
+ @Test
+ fun maxGammaBrightness() {
+ assertThat(underTest.maxBrightness)
+ .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX))
+ }
+
+ @Test
+ fun minGammaBrightness() {
+ assertThat(underTest.minBrightness)
+ .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN))
+ }
+
+ @Test
+ fun dragging_temporaryBrightnessSet_currentBrightnessDoesntChange() =
+ with(kosmos) {
+ testScope.runTest {
+ val temporaryBrightness by
+ collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
+ val brightness by collectLastValue(underTest.currentBrightness)
+
+ val newBrightness = underTest.maxBrightness.value / 3
+ val expectedTemporaryBrightness =
+ BrightnessUtils.convertGammaToLinearFloat(
+ newBrightness,
+ minBrightness,
+ maxBrightness
+ )
+ val drag = Drag.Dragging(GammaBrightness(newBrightness))
+
+ underTest.onDrag(drag)
+
+ assertThat(temporaryBrightness!!.floatValue)
+ .isWithin(1e-5f)
+ .of(expectedTemporaryBrightness)
+ assertThat(brightness!!.value).isNotEqualTo(newBrightness)
+ }
+ }
+
+ @Test
+ fun draggingStopped_currentBrightnessChanges() =
+ with(kosmos) {
+ testScope.runTest {
+ val brightness by collectLastValue(underTest.currentBrightness)
+
+ val newBrightness = underTest.maxBrightness.value / 3
+ val drag = Drag.Stopped(GammaBrightness(newBrightness))
+
+ underTest.onDrag(drag)
+
+ assertThat(brightness!!.value).isEqualTo(newBrightness)
+ }
+ }
+
+ @Test
+ fun label() {
+ assertThat(underTest.label)
+ .isEqualTo(Text.Resource(R.string.quick_settings_brightness_dialog_title))
+ }
+
+ @Test
+ fun icon() {
+ assertThat(underTest.icon)
+ .isEqualTo(
+ Icon.Resource(
+ R.drawable.ic_brightness_full,
+ ContentDescription.Resource(underTest.label.res),
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 6bff0dc7bd9e..5e120b5f9560 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -16,11 +16,15 @@
package com.android.systemui.communal.data.repository
+import android.content.Context
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
@@ -34,16 +38,22 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.FakeSharedPreferences
import com.google.common.truth.Truth.assertThat
import java.io.File
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -69,20 +79,12 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
USER_INFOS[1].id to FakeSharedPreferences()
)
)
- underTest =
- CommunalPrefsRepositoryImpl(
- testScope.backgroundScope,
- kosmos.testDispatcher,
- userRepository,
- userFileManager,
- logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
- tableLogBuffer,
- )
}
@Test
fun isCtaDismissedValue_byDefault_isFalse() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
assertThat(isCtaDismissed).isFalse()
}
@@ -90,6 +92,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Test
fun isCtaDismissedValue_onSet_isTrue() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
underTest.setCtaDismissedForCurrentUser()
@@ -99,6 +102,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Test
fun isCtaDismissedValue_whenSwitchUser() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
underTest.setCtaDismissedForCurrentUser()
@@ -118,6 +122,44 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
assertThat(isCtaDismissed).isTrue()
}
+ @Test
+ fun getSharedPreferences_whenFileRestored() =
+ testScope.runTest {
+ val userFileManagerSpy = Mockito.spy(userFileManager)
+ underTest = createCommunalPrefsRepositoryImpl(userFileManagerSpy)
+
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(isCtaDismissed).isFalse()
+ clearInvocations(userFileManagerSpy)
+
+ // Received restore finished event.
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(BackupHelper.ACTION_RESTORE_FINISHED),
+ )
+ runCurrent()
+
+ // Get shared preferences from the restored file.
+ verify(userFileManagerSpy, atLeastOnce())
+ .getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userRepository.getSelectedUserInfo().id
+ )
+ }
+
+ private fun createCommunalPrefsRepositoryImpl(userFileManager: UserFileManager) =
+ CommunalPrefsRepositoryImpl(
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ userRepository,
+ userFileManager,
+ kosmos.broadcastDispatcher,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
+ )
+
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
UserFileManager {
override fun getFile(fileName: String, userId: Int): File {
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 6b2831991416..f71121c43ff8 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
@@ -25,13 +25,13 @@ import android.content.Intent
import android.content.pm.UserInfo
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+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.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_ENABLED
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -95,15 +95,27 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
@Test
fun hubIsDisabledByUser() =
testScope.runTest {
- kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 0,
+ PRIMARY_USER.id
+ )
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isFalse()
assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
- kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 1,
+ SECONDARY_USER.id
+ )
assertThat(enabledState?.enabled).isFalse()
- kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 1,
+ PRIMARY_USER.id
+ )
assertThat(enabledState?.enabled).isTrue()
}
@@ -126,7 +138,11 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
assertThat(enabledState?.enabled).isTrue()
- kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 0,
+ PRIMARY_USER.id
+ )
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
assertThat(enabledState?.enabled).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 2c9d72c423bc..6cae5d352fc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -19,11 +19,11 @@ package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
import android.os.UserHandle
+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.SysuiTestCase
-import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -220,7 +220,11 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
fakeKeyguardRepository.setKeyguardShowing(true)
val settingsValue = if (available) 1 else 0
- fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id)
+ fakeSettings.putIntForUser(
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ settingsValue,
+ MAIN_USER_INFO.id
+ )
}
private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 36919d0c74a4..9a13bb261aa7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -827,7 +827,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+ fun isAuthenticatedIsResetToFalseWhenTransitioningToGone() =
testScope.runTest {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
@@ -840,7 +840,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
assertThat(authenticated()).isTrue()
- keyguardRepository.keyguardDoneAnimationsFinished()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
assertThat(authenticated()).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 9536084fdb23..73373d553560 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -5,15 +5,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -25,7 +21,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -37,12 +32,10 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
- @Mock private lateinit var keyguardStateController: KeyguardStateController
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val userRepository = FakeUserRepository()
- private val keyguardRepository = FakeKeyguardRepository()
private lateinit var underTest: DeviceEntryRepository
@@ -59,35 +52,9 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
- keyguardStateController = keyguardStateController,
- keyguardRepository = keyguardRepository,
)
testScope.runCurrent()
}
-
- @Test
- fun isUnlocked() =
- testScope.runTest {
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
-
- runCurrent()
- assertThat(isUnlocked).isFalse()
-
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController, Mockito.atLeastOnce()).addCallback(captor.capture())
-
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
- captor.value.onUnlockedChanged()
- runCurrent()
- assertThat(isUnlocked).isTrue()
-
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- runCurrent()
- assertThat(isUnlocked).isFalse()
- }
-
@Test
fun isLockscreenEnabled() =
testScope.runTest {
@@ -102,17 +69,6 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
}
@Test
- fun reportSuccessfulAuthentication_updatesIsUnlocked() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isFalse()
-
- underTest.reportSuccessfulAuthentication()
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
fun isBypassEnabled_disabledInController() =
testScope.runTest {
whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 70ceb2a75d7c..5caf35ba90d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -41,8 +41,10 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -89,22 +91,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
- kosmos.fakeDeviceEntryRepository.apply {
- setLockscreenEnabled(false)
-
- // Toggle isUnlocked, twice.
- //
- // This is done because the underTest.isUnlocked flow doesn't receive values from
- // just changing the state above; the actual isUnlocked state needs to change to
- // cause the logic under test to "pick up" the current state again.
- //
- // It is done twice to make sure that we don't actually change the isUnlocked state
- // from what it originally was.
- setUnlocked(!isUnlocked.value)
- runCurrent()
- setUnlocked(!isUnlocked.value)
- runCurrent()
- }
+ kosmos.fakeDeviceEntryRepository.apply { setLockscreenEnabled(false) }
+ runCurrent()
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isTrue()
@@ -125,7 +113,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Sim
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
@@ -271,7 +261,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -283,7 +272,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
@@ -295,7 +283,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -307,7 +297,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
@@ -333,7 +325,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
underTest.attemptDeviceEntry()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 51db4513bb39..a7a7bea313fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,16 +25,30 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class DeviceUnlockedInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -46,71 +61,170 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
applicationScope = testScope.backgroundScope,
authenticationInteractor = kosmos.authenticationInteractor,
deviceEntryRepository = deviceEntryRepository,
+ trustInteractor = kosmos.trustInteractor,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
+ powerInteractor = kosmos.powerInteractor,
)
+ @Before
+ fun setup() {
+ kosmos.fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
+ }
+
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsNone_isTrue() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsNone_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- assertThat(isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
}
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsPin_isTrue() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPin_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-
- assertThat(isUnlocked).isTrue()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.Fingerprint)
}
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsSim_isFalse() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsNone_isTrue() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsNone_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- assertThat(isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsPin_isFalse() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsPin_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsSim_isFalse() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+
+ @Test
+ fun deviceUnlockStatus_whenFaceIsAuthenticatedWhileAwakeWithBypass_isTrue() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.powerInteractor.setAwakeForTest()
+
+ kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.FaceWithBypass)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenFaceIsAuthenticatedWithoutBypass_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.FaceWithoutBypass)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenFingerprintIsAuthenticated_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.Fingerprint)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenUnlockedByTrustAgent_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.fakeUserRepository.setSelectedUserInfo(
+ primaryUser,
+ SelectionStatus.SELECTION_COMPLETE
+ )
+
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.TrustAgent)
+ }
+
+ @Test
+ fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ companion object {
+ private const val primaryUserId = 1
+ private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
+
+ private val secondaryUser = UserInfo(2, "secondary user", 0)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 6a86801cba90..0f8fc3824e3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -63,7 +63,6 @@ import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.Optional;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@@ -119,6 +118,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
private static final float TOUCH_REGION = .3f;
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -142,6 +142,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
mUiEventLogger);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -160,9 +161,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
*/
@Test
public void testSessionStart() {
- mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion);
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
- verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION));
+ verify(mRegion).union(mRectCaptor.capture());
final Rect bounds = mRectCaptor.getValue();
final Rect expected = new Rect();
@@ -194,6 +195,85 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
UP,
}
+ @Test
+ public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+ new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
+ verify(mRegion).union(mRectCaptor.capture());
+ final Rect bounds = mRectCaptor.getValue();
+
+ final Rect expected = new Rect();
+ final float minBouncerHeight =
+ SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT;
+ final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight);
+
+ expected.set(0, minAllowableBottom , SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ @Test
+ public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+ new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
+ verify(mRegion).union(mRectCaptor.capture());
+ final Rect bounds = mRectCaptor.getValue();
+
+ final Rect expected = new Rect();
+ final int gestureAreaTop = SCREEN_HEIGHT_PX - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+ expected.set(0, gestureAreaTop, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ @Test
+ public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
+ final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
+ - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+ final int minimumSwipeAreaTop = SCREEN_HEIGHT_PX
+ - Math.round(SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT);
+
+ Rect exclusionRect = new Rect(0, 0, SCREEN_WIDTH_PX,
+ (normalSwipeAreaTop + minimumSwipeAreaTop) / 2);
+
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, exclusionRect);
+
+ verify(mRegion).union(mRectCaptor.capture());
+
+ final Rect bounds = mRectCaptor.getValue();
+ final Rect expected = new Rect();
+
+ final int expectedSwipeAreaBottom = exclusionRect.bottom;
+ expected.set(0, expectedSwipeAreaBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
+ DreamTouchHandler.TouchSession touchSession,
+ NotificationShadeWindowController notificationShadeWindowController) {
+ touchHandler.onSessionStart(touchSession);
+ verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
+ ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(touchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(touchSession).registerInputListener(eventListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
/**
* Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index eec74efd4751..6d7a0a96a71b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -214,14 +214,16 @@ class KeyguardInteractorTest : SysuiTestCase() {
)
repository.setStatusBarState(StatusBarState.KEYGUARD)
- shadeRepository.setLegacyShadeExpansion(1f)
+ // User begins to swipe up
+ shadeRepository.setLegacyShadeExpansion(0.99f)
// When not dismissable, no alpha value (null) should emit
repository.setKeyguardDismissible(false)
assertThat(dismissAlpha).isNull()
repository.setKeyguardDismissible(true)
- assertThat(dismissAlpha).isGreaterThan(0.95f)
+ shadeRepository.setLegacyShadeExpansion(0.98f)
+ assertThat(dismissAlpha).isGreaterThan(0.5f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 056a401a2644..0f5e45814608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -37,14 +37,17 @@ package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -247,14 +250,20 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
val occludingActivityWillDismissKeyguard by
collectLastValue(underTest.occludingActivityWillDismissKeyguard)
assertThat(occludingActivityWillDismissKeyguard).isFalse()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
// Unlock device:
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(occludingActivityWillDismissKeyguard).isTrue()
// Re-lock device:
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.powerInteractor.setAsleepForTest()
runCurrent()
assertThat(occludingActivityWillDismissKeyguard).isFalse()
}
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 f517cec040a0..31337a635bfb 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
@@ -23,6 +23,7 @@ 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.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.burnInInteractor
@@ -60,10 +61,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private lateinit var underTest: AodBurnInViewModel
- private var burnInParameters =
- BurnInParameters(
- clockControllerProvider = { clockController },
- )
+ private var burnInParameters = BurnInParameters()
private val burnInFlow = MutableStateFlow(BurnInModel())
@Before
@@ -76,6 +74,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
.thenReturn(emptyFlow())
kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel
+ kosmos.fakeKeyguardClockRepository.setCurrentClock(clockController)
underTest = kosmos.aodBurnInViewModel
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 2fd2ef1f3240..66f7e015a133 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -121,7 +121,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
AuthenticationMethodModel.Pin
}
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
kosmos.shadeRepository.setShadeMode(
if (isSingleShade) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
index 28995e1feb0e..9656511817dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
@@ -17,10 +17,16 @@
package com.android.systemui.media.controls.domain.interactor
import android.R
+import android.content.ComponentName
+import android.content.Intent
+import android.content.applicationContext
import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
+import com.android.systemui.broadcast.broadcastSender
+import com.android.systemui.broadcast.mockBroadcastSender
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -28,25 +34,36 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor.Companion.EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaRecModel
import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
class MediaRecommendationsInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val spyContext = spy(context)
+ private val kosmos = testKosmos().apply { applicationContext = spyContext }
private val testScope = kosmos.testScope
private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val activityStarter = kosmos.activityStarter
private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
private val smartspaceMediaData: SmartspaceMediaData =
SmartspaceMediaData(
@@ -56,7 +73,11 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {
recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
- private val underTest: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor
+ private val underTest: MediaRecommendationsInteractor =
+ with(kosmos) {
+ broadcastSender = mockBroadcastSender
+ kosmos.mediaRecommendationsInteractor
+ }
@Test
fun addRecommendation_smartspaceMediaDataUpdate() =
@@ -111,6 +132,50 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {
assertThat(recommendations?.mediaRecs?.isEmpty()).isTrue()
}
+ @Test
+ fun removeRecommendation_noTrampolineActivity() {
+ val intent = Intent()
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
+
+ verify(kosmos.mockBroadcastSender).sendBroadcast(eq(intent))
+ }
+
+ @Test
+ fun removeRecommendation_usingTrampolineActivity() {
+ doNothing().whenever(spyContext).startActivity(any())
+ val intent = Intent()
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.component = ComponentName(PACKAGE_NAME, EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)
+
+ underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
+
+ verify(spyContext).startActivity(eq(intent))
+ }
+
+ @Test
+ fun startSettings() {
+ underTest.startSettings()
+
+ verify(activityStarter).startActivity(any(), eq(true))
+ }
+
+ @Test
+ fun startClickIntent() {
+ doNothing().whenever(spyContext).startActivity(any())
+ val intent = Intent()
+ val expandable = mock<Expandable>()
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ underTest.startClickIntent(expandable, intent)
+
+ verify(spyContext).startActivity(eq(intent))
+ }
+
companion object {
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
private const val PACKAGE_NAME = "com.example.app"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
new file mode 100644
index 000000000000..51b1911be5d5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.R
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Icon
+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.media.controls.MediaTestHelper
+import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaRecommendationsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val packageManager = kosmos.packageManager
+ private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ private val drawable = context.getDrawable(R.drawable.ic_media_play)
+ private val smartspaceMediaData: SmartspaceMediaData =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ packageName = PACKAGE_NAME,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+
+ private val underTest: MediaRecommendationsViewModel = kosmos.mediaRecommendationsViewModel
+
+ @Test
+ fun loadRecommendations_recsCardViewModelIsLoaded() =
+ testScope.runTest {
+ whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
+ whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
+ .thenReturn(drawable)
+ whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
+ .thenReturn(ApplicationInfo())
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
+ val recsCardViewModel by collectLastValue(underTest.mediaRecsCard)
+
+ context.setMockPackageManager(packageManager)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
+
+ assertThat(recsCardViewModel).isNotNull()
+ assertThat(recsCardViewModel?.mediaRecs?.size)
+ .isEqualTo(smartspaceMediaData.recommendations.size)
+ }
+
+ companion object {
+ private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+ private const val PACKAGE_NAME = "com.example.app"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index 4a39ba234c67..b7e08da42afd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -104,6 +104,7 @@ class QSTileLoggerTest : SysuiTestCase() {
underTest.logUserActionRejectedByPolicy(
QSTileUserAction.Click(null),
TileSpec.create("test_spec"),
+ "test_restriction",
)
assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by policy")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt
new file mode 100644
index 000000000000..d0cd56fce778
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.interactor
+
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val mockSensorPrivacyController =
+ mock<IndividualSensorPrivacyController> {
+ whenever(isSensorBlocked(eq(CAMERA))).thenReturn(false) // determines initial value
+ }
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ SensorPrivacyToggleTileDataInteractor(
+ testScope.testScheduler,
+ mockSensorPrivacyController,
+ CAMERA
+ )
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ whenever(mockSensorPrivacyController.supportsSensorToggle(eq(CAMERA))).thenReturn(true)
+
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+ runCurrent()
+
+ assertThat(availability).hasSize(1)
+ assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesPrivacyControllerIsSensorBlocked() =
+ testScope.runTest {
+ val callbackCaptor = argumentCaptor<IndividualSensorPrivacyController.Callback>()
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+ verify(mockSensorPrivacyController).addCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse()
+
+ callback.onSensorBlockedChanged(CAMERA, true)
+ runCurrent()
+ assertThat(data!!.isBlocked).isTrue()
+
+ callback.onSensorBlockedChanged(CAMERA, false)
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse()
+
+ callback.onSensorBlockedChanged(MICROPHONE, true)
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse() // We're NOT listening for MIC sensor changes
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..562e6ebcc029
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.interactor
+
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ // The keyguard repository below is the same one kosmos used to create the interactor above
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val mockActivityStarter = kosmos.activityStarter
+ private val mockSensorPrivacyController = mock<IndividualSensorPrivacyController>()
+ private val fakeSafetyCenterManager = mock<SafetyCenterManager>()
+
+ private val underTest =
+ SensorPrivacyToggleTileUserActionInteractor(
+ inputHandler,
+ keyguardInteractor,
+ mockActivityStarter,
+ mockSensorPrivacyController,
+ fakeSafetyCenterManager,
+ CAMERA
+ )
+
+ @Test
+ fun handleClickWhenNotBlocked() = runTest {
+ val originalIsBlocked = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ }
+
+ @Test
+ fun handleClickWhenBlocked() = runTest {
+ val originalIsBlocked = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ }
+
+ @Test
+ fun handleClick_whenKeyguardIsDismissableAndShowing_whenControllerRequiresAuth() = runTest {
+ whenever(mockSensorPrivacyController.requiresAuthentication()).thenReturn(true)
+ keyguardRepository.setKeyguardDismissible(true)
+ keyguardRepository.setKeyguardShowing(true)
+ val originalIsBlocked = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController, never())
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ verify(mockActivityStarter).postQSRunnableDismissingKeyguard(any())
+ }
+
+ @Test
+ fun handleLongClick_whenSafetyManagerEnabled_privacyControlsIntent() = runTest {
+ whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ }
+ }
+
+ @Test
+ fun handleLongClick_whenSafetyManagerDisabled_privacySettingsIntent() = runTest {
+ whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleClick_microphone_flipsController() = runTest {
+ val micUserActionInteractor =
+ SensorPrivacyToggleTileUserActionInteractor(
+ inputHandler,
+ keyguardInteractor,
+ mockActivityStarter,
+ mockSensorPrivacyController,
+ fakeSafetyCenterManager,
+ MICROPHONE
+ )
+
+ micUserActionInteractor.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(false))
+ )
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(true))
+
+ micUserActionInteractor.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(true))
+ )
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(false))
+ }
+}
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
new file mode 100644
index 000000000000..5e7aadcda6db
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsMicrophoneSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.CameraPrivacyTileResources
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.MicrophonePrivacyTileResources
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val cameraConfig = kosmos.qsCameraSensorPrivacyToggleTileConfig
+ private val micConfig = kosmos.qsMicrophoneSensorPrivacyToggleTileConfig
+
+ @Test
+ fun mapCamera_notBlocked() {
+ val mapper = createMapper(CameraPrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(false)
+
+ val outputState = mapper.map(cameraConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_available),
+ R.drawable.qs_camera_access_icon_on,
+ null,
+ CAMERA
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapCamera_blocked() {
+ val mapper = createMapper(CameraPrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(true)
+
+ val outputState = mapper.map(cameraConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_blocked),
+ R.drawable.qs_camera_access_icon_off,
+ null,
+ CAMERA
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapMic_notBlocked() {
+ val mapper = createMapper(MicrophonePrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(false)
+
+ val outputState = mapper.map(micConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_available),
+ R.drawable.qs_mic_access_on,
+ null,
+ MICROPHONE
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapMic_blocked() {
+ val mapper = createMapper(MicrophonePrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(true)
+
+ val outputState = mapper.map(micConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_blocked),
+ R.drawable.qs_mic_access_off,
+ null,
+ MICROPHONE
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createMapper(
+ sensorResources: SensorPrivacyTileResources
+ ): SensorPrivacyToggleTileMapper {
+ val mapper =
+ SensorPrivacyToggleTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_camera_access_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_camera_access_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_mic_access_off, TestStubDrawable())
+ addOverride(R.drawable.qs_mic_access_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ sensorResources,
+ )
+ return mapper
+ }
+
+ private fun createSensorPrivacyToggleTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ sensorId: Int,
+ ): QSTileState {
+ val label =
+ if (sensorId == CAMERA) context.getString(R.string.quick_settings_camera_label)
+ else context.getString(R.string.quick_settings_mic_label)
+
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ stateDescription,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index a8bc8d6b36b6..9ce2e0f87499 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -60,7 +60,9 @@ class QSTileViewModelTest : SysuiTestCase() {
@Mock private lateinit var qsTileAnalytics: QSTileAnalytics
private val tileConfig =
- QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+ QSTileConfigTestBuilder.build {
+ policy = QSTilePolicy.Restricted(listOf("test_restriction"))
+ }
private val userRepository = FakeUserRepository()
private val tileDataInteractor = FakeQSTileDataInteractor<String>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 18cdd71e25b6..6066d24c2214 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -16,18 +16,17 @@
package com.android.systemui.qs.tiles.viewmodel
-import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import com.android.settingslib.RestrictedLockUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor.Companion.DISABLED_RESTRICTION
+import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor.Companion.ENABLED_RESTRICTION
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -54,7 +53,6 @@ import org.mockito.MockitoAnnotations
/** Tests all possible [QSTileUserAction]s. If you need */
@MediumTest
-@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class QSTileViewModelUserInputTest : SysuiTestCase() {
@@ -65,8 +63,10 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
// TODO(b/299909989): this should be parametrised. b/299096521 blocks this.
private val userAction: QSTileUserAction = QSTileUserAction.Click(null)
- private val tileConfig =
- QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") }
+ private var tileConfig =
+ QSTileConfigTestBuilder.build {
+ policy = QSTilePolicy.Restricted(listOf(ENABLED_RESTRICTION))
+ }
private val userRepository = FakeUserRepository()
private val tileDataInteractor = FakeQSTileDataInteractor<String>()
@@ -112,11 +112,77 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
@Test
fun disabledByPolicyUserInputIsSkipped() =
testScope.runTest {
+ tileConfig =
+ QSTileConfigTestBuilder.build {
+ policy = QSTilePolicy.Restricted(listOf(DISABLED_RESTRICTION))
+ }
+ underTest = createViewModel(testScope)
underTest.state.launchIn(backgroundScope)
- disabledByPolicyInteractor.policyResult =
- DisabledByPolicyInteractor.PolicyResult.TileDisabled(
- RestrictedLockUtils.EnforcedAdmin()
+
+ runCurrent()
+
+ underTest.onActionPerformed(userAction)
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+ verify(qsTileLogger)
+ .logUserActionRejectedByPolicy(
+ eq(userAction),
+ eq(tileConfig.tileSpec),
+ eq(DISABLED_RESTRICTION)
+ )
+ verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+ }
+
+ @Test
+ fun disabledByPolicySecondRestriction_userInputIsSkipped() =
+ testScope.runTest {
+ tileConfig =
+ QSTileConfigTestBuilder.build {
+ policy =
+ QSTilePolicy.Restricted(listOf(ENABLED_RESTRICTION, DISABLED_RESTRICTION))
+ }
+
+ underTest = createViewModel(testScope)
+
+ underTest.state.launchIn(backgroundScope)
+
+ runCurrent()
+
+ underTest.onActionPerformed(userAction)
+ runCurrent()
+
+ assertThat(tileDataInteractor.triggers.last())
+ .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
+ verify(qsTileLogger)
+ .logUserActionRejectedByPolicy(
+ eq(userAction),
+ eq(tileConfig.tileSpec),
+ eq(DISABLED_RESTRICTION)
)
+ verify(qsTileAnalytics, never()).trackUserAction(any(), any())
+ }
+
+ /** This tests that the policies are applied sequentially */
+ @Test
+ fun disabledByPolicySecondRestriction_onlyFirstIsTriggered() =
+ testScope.runTest {
+ tileConfig =
+ QSTileConfigTestBuilder.build {
+ policy =
+ QSTilePolicy.Restricted(
+ listOf(
+ DISABLED_RESTRICTION,
+ FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2
+ )
+ )
+ }
+
+ underTest = createViewModel(testScope)
+
+ underTest.state.launchIn(backgroundScope)
+
runCurrent()
underTest.onActionPerformed(userAction)
@@ -125,7 +191,17 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
assertThat(tileDataInteractor.triggers.last())
.isNotInstanceOf(DataUpdateTrigger.UserInput::class.java)
verify(qsTileLogger)
- .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec))
+ .logUserActionRejectedByPolicy(
+ eq(userAction),
+ eq(tileConfig.tileSpec),
+ eq(DISABLED_RESTRICTION)
+ )
+ verify(qsTileLogger, never())
+ .logUserActionRejectedByPolicy(
+ eq(userAction),
+ eq(tileConfig.tileSpec),
+ eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2)
+ )
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 27c4ec125b59..f2eb7f44600e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
@@ -496,4 +497,33 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
runCurrent()
verify(qsImpl!!).setInSplitShade(true)
}
+
+ @Test
+ fun requestCloseCustomizer() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ underTest.requestCloseCustomizer()
+ verify(qsImpl!!).closeCustomizer()
+ }
+
+ @Test
+ fun setBrightnessMirrorController() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val mirrorController = mock<MirrorController>()
+ underTest.setBrightnessMirrorController(mirrorController)
+
+ verify(qsImpl!!).setBrightnessMirrorController(mirrorController)
+
+ underTest.setBrightnessMirrorController(null)
+ verify(qsImpl!!).setBrightnessMirrorController(null)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index ef385673c950..426f94d3e96a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -108,6 +109,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
underTest =
QuickSettingsSceneViewModel(
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
@@ -133,18 +135,13 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
}
@Test
- fun destinationsCustomizing() =
+ fun destinationsCustomizing_noDestinations() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(true)
- assertThat(destinations)
- .isEqualTo(
- mapOf(
- Back to UserActionResult(Scenes.QuickSettings),
- )
- )
+ assertThat(destinations).isEmpty()
}
@Test
@@ -164,18 +161,13 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
}
@Test
- fun destinations_whenCustomizing_inSplitShade() =
+ fun destinations_whenCustomizing_inSplitShade_noDestinations() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, true)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(true)
- assertThat(destinations)
- .isEqualTo(
- mapOf(
- Back to UserActionResult(Scenes.QuickSettings),
- )
- )
+ assertThat(destinations).isEmpty()
}
@Test
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 fd685192fdab..9856f9050c4b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -50,6 +50,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -73,6 +74,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -258,6 +260,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsController = kosmos.footerActionsController,
@@ -265,8 +268,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
val startable =
@@ -291,6 +292,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
)
startable.start()
@@ -371,8 +373,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+ val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
+
+ assertThat(canSwipeToEnter).isTrue()
assertCurrentScene(Scenes.Lockscreen)
// Emulate a user swipe to dismiss the lockscreen.
@@ -437,17 +442,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
@Test
- fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
- testScope.runTest {
- unlockDevice()
- assertCurrentScene(Scenes.Gone)
- putDeviceToSleep(instantlyLockDevice = false)
- assertCurrentScene(Scenes.Lockscreen)
- wakeUpDevice()
- assertCurrentScene(Scenes.Gone)
- }
-
- @Test
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
@@ -540,7 +534,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fakeSceneDataSource.pause()
introduceLockedSim()
emulatePendingTransitionProgress(expectedVisible = true)
- enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None)
+ enterSimPin(
+ authMethodAfterSimUnlock = AuthenticationMethodModel.None,
+ enableLockscreen = false
+ )
+
assertCurrentScene(Scenes.Gone)
}
@@ -708,7 +706,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(authMethod.isSecure)
.isTrue()
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
}
@@ -721,9 +718,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
emulateUserDrivenTransition(Scenes.Bouncer)
fakeSceneDataSource.pause()
enterPin()
- // This repository state is not changed by the AuthInteractor, it relies on
- // KeyguardStateController.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+
emulatePendingTransitionProgress(
expectedVisible = false,
)
@@ -763,7 +758,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
* Does not assert that the device is locked or unlocked.
*/
private fun TestScope.enterSimPin(
- authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None
+ authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None,
+ enableLockscreen: Boolean = true,
) {
assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
.that(getCurrentSceneInUi())
@@ -778,9 +774,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
pinBouncerViewModel.onPinButtonClicked(digit)
}
pinBouncerViewModel.onAuthenticateButtonClicked()
- setAuthMethod(authMethodAfterSimUnlock)
kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
+
+ setAuthMethod(authMethodAfterSimUnlock, enableLockscreen)
+ runCurrent()
}
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 3d6619272dbe..ae31058e7a54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -140,4 +140,23 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val underTest = kosmos.sceneContainerRepository
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(Scenes.Shade)
+ assertThat(previousScene).isEqualTo(firstScene)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ underTest.changeScene(Scenes.QuickSettings)
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index f645f1cc4369..b179c30e60b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -23,7 +23,8 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
@@ -79,7 +80,9 @@ class SceneInteractorTest : SysuiTestCase() {
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
underTest.changeScene(Scenes.Gone, "reason")
@@ -88,11 +91,7 @@ class SceneInteractorTest : SysuiTestCase() {
@Test(expected = IllegalStateException::class)
fun changeScene_toGoneWhenStillLocked_throws() =
- testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
- underTest.changeScene(Scenes.Gone, "reason")
- }
+ testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") }
@Test
fun sceneChanged_inDataSource() =
@@ -292,4 +291,19 @@ class SceneInteractorTest : SysuiTestCase() {
assertThat(isVisible).isFalse()
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(toScene = Scenes.Shade, "reason")
+ assertThat(previousScene).isEqualTo(firstScene)
+
+ underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index e330a359add5..3fd5306abb71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -39,9 +39,13 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -135,6 +139,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
)
}
@@ -145,6 +150,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val isVisible by collectLastValue(sceneInteractor.isVisible)
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -198,6 +204,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Lockscreen,
isDeviceProvisioned = false,
@@ -249,14 +256,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
+ startsAwake = false,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
}
@@ -265,13 +272,16 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = false,
initialSceneKey = Scenes.Bouncer,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -281,13 +291,16 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isBypassEnabled = true,
initialSceneKey = Scenes.Lockscreen,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -305,7 +318,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
}
@@ -327,7 +339,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade)
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
@@ -346,7 +360,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -383,7 +396,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
.forEachIndexed { index, sceneKey ->
if (sceneKey == Scenes.Gone) {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
}
fakeSceneDataSource.pause()
@@ -453,6 +468,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
powerInteractor.setAwakeForTest()
+ runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -529,7 +545,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -564,7 +582,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
// Changing to the Gone scene should report a successful unlock.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
runCurrent()
@@ -759,7 +779,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
runCurrent()
verify(falsingCollector).onBouncerShown()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
runCurrent()
@@ -830,6 +852,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -985,6 +1008,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -1147,6 +1171,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
assert(isLockscreenEnabled) {
"Lockscreen cannot be disabled while having a secure authentication method"
}
+ if (isDeviceUnlocked) {
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
}
check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
@@ -1154,8 +1183,13 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
sceneContainerFlags.enabled = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ authenticationMethod?.let {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
+ isLockscreenEnabled = isLockscreenEnabled
+ )
+ }
runCurrent()
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
@@ -1166,12 +1200,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlow.value = ObservableTransitionState.Idle(it)
sceneInteractor.changeScene(it, "reason")
}
- authenticationMethod?.let {
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
- isLockscreenEnabled = isLockscreenEnabled
- )
- }
if (startsAwake) {
powerInteractor.setAwakeForTest()
} else {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
new file mode 100644
index 000000000000..a1af70b316ee
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest = BrightnessMirrorShowingRepository()
+
+ @Test
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
+
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
new file mode 100644
index 000000000000..31d6df250af3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest =
+ BrightnessMirrorShowingInteractor(kosmos.brightnessMirrorShowingRepository)
+
+ @Test
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
+
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
new file mode 100644
index 000000000000..6de7f403a745
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.applicationContext
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorInflaterTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ @Test
+ fun inflate_sliderViewAddedToFrame() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory
+ )
+
+ assertThat(sliderController.rootView.parent).isSameInstanceAs(frame)
+
+ Assert.setTestThread(null)
+ }
+
+ @Test
+ fun inflate_frameAndSliderViewVisible() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory,
+ )
+
+ assertThat(frame.visibility).isEqualTo(View.VISIBLE)
+ assertThat(sliderController.rootView.visibility).isEqualTo(View.VISIBLE)
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
new file mode 100644
index 000000000000..09fc6f9ad96d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
+
+ @Test
+ fun showHideMirror_isShowing() =
+ with(kosmos) {
+ testScope.runTest {
+ val showing by collectLastValue(underTest.isShowing)
+
+ assertThat(showing).isFalse()
+
+ underTest.showMirror()
+ assertThat(showing).isTrue()
+
+ underTest.hideMirror()
+ assertThat(showing).isFalse()
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_correctLocationAndSize() =
+ with(kosmos) {
+ testScope.runTest {
+ val locationAndSize by collectLastValue(underTest.locationAndSize)
+
+ val x = 20
+ val y = 100
+ val height = 50
+ val width = 200
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val mockView =
+ mock<View> {
+ whenever(getLocationInWindow(any())).then {
+ it.getArgument<IntArray>(0).apply {
+ this[0] = x
+ this[1] = y
+ }
+ Unit
+ }
+
+ whenever(measuredHeight).thenReturn(height)
+ whenever(measuredWidth).thenReturn(width)
+ }
+
+ underTest.setLocationAndSize(mockView)
+
+ assertThat(locationAndSize)
+ .isEqualTo(
+ // Adjust for padding around the view
+ LocationAndSize(
+ yOffset = y - padding,
+ width = width + 2 * padding,
+ height = height + 2 * padding,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_paddingSetToRootView() =
+ with(kosmos) {
+ Assert.setTestThread(Thread.currentThread())
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val view = mock<View>()
+
+ val (_, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ brightnessSliderControllerFactory,
+ )
+ underTest.setToggleSlider(sliderController)
+
+ underTest.setLocationAndSize(view)
+
+ with(sliderController.rootView) {
+ assertThat(paddingBottom).isEqualTo(padding)
+ assertThat(paddingTop).isEqualTo(padding)
+ assertThat(paddingLeft).isEqualTo(padding)
+ assertThat(paddingRight).isEqualTo(padding)
+ }
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index cd79ed1a8965..cbbcce96873b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -21,10 +21,11 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
@@ -38,6 +39,7 @@ import com.android.systemui.util.mockito.mock
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.Before
@@ -53,7 +55,7 @@ import org.mockito.Mockito.verify
class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
@@ -68,7 +70,9 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
set(Flags.NSSL_DEBUG_LINES, false)
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
}
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
testScope.runCurrent()
shadeInteractor = kosmos.shadeInteractor
underTest = kosmos.shadeControllerSceneImpl
@@ -161,6 +165,10 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
testScope.runTest {
// GIVEN shade is collapsed and a post-collapse action is enqueued
val testRunnable = mock<Runnable>()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
setCollapsed()
underTest.postOnShadeExpanded(testRunnable)
@@ -179,7 +187,14 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
testScope.runCurrent()
}
- private fun setDeviceEntered(isEntered: Boolean) {
+ private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+ if (isEntered) {
+ // Unlock the device marking the device has entered.
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ }
setScene(
if (isEntered) {
Scenes.Gone
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index ad40f8eab4f6..6485c475eeb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -24,9 +24,12 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -49,7 +52,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
private val sceneInteractor = kosmos.sceneInteractor
private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
@@ -71,7 +73,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
fun legacyPanelExpansion_whenIdle_whenLocked() =
testScope.runTest {
underTest = kosmos.panelExpansionInteractorImpl
- setUnlocked(false)
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
@@ -95,7 +96,15 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
fun legacyPanelExpansion_whenIdle_whenUnlocked() =
testScope.runTest {
underTest = kosmos.panelExpansionInteractorImpl
- setUnlocked(true)
+ val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(unlockStatus)
+ .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint))
+
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
@@ -147,14 +156,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
}
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
private fun TestScope.changeScene(
toScene: SceneKey,
assertDuringProgress: ((progress: Float) -> Unit) = {},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index d309c6bcfd58..e759b504d5c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -48,7 +47,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
val testScope = kosmos.testScope
val sceneInteractor = kosmos.sceneInteractor
- val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
val underTest = kosmos.shadeBackActionInteractor
@Before
@@ -78,7 +76,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
@Test
fun animateCollapseQs_fullyCollapse_locked() =
testScope.runTest {
- deviceEntryRepository.setUnlocked(false)
setScene(Scenes.QuickSettings)
underTest.animateCollapseQs(true)
runCurrent()
@@ -95,7 +92,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
}
private fun enterDevice() {
- deviceEntryRepository.setUnlocked(true)
testScope.runCurrent()
setScene(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 52caa787bb2f..2ab934c2386e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -21,11 +21,15 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
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.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -40,6 +44,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -48,6 +53,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeStartableTest : SysuiTestCase() {
@@ -95,7 +101,14 @@ class ShadeStartableTest : SysuiTestCase() {
underTest.start()
- setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Gone)
@@ -120,14 +133,6 @@ class ShadeStartableTest : SysuiTestCase() {
}
}
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
private fun TestScope.changeScene(
toScene: SceneKey,
transitionState: MutableStateFlow<ObservableTransitionState>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 1d6b22309579..7a681b383aad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -29,6 +29,8 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.qs.footerActionsController
@@ -37,6 +39,7 @@ import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
@@ -125,6 +128,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
@@ -140,7 +144,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
.isEqualTo(Scenes.Lockscreen)
@@ -153,7 +156,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
.isEqualTo(Scenes.Gone)
@@ -178,7 +183,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
@@ -196,7 +200,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(isClickable).isFalse()
@@ -209,7 +215,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
assertThat(isClickable).isTrue()
@@ -222,7 +227,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 53522e276112..a3cf92986bd6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -31,7 +31,8 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -57,27 +58,44 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
}
private val testScope = kosmos.testScope
private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
- private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel }
+ private val appearanceViewModel by lazy { kosmos.notificationScrollViewModel }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
@Test
fun updateBounds() =
testScope.runTest {
- val clipping by collectLastValue(appearanceViewModel.shadeScrimClipping)
-
- val top = 200f
- val left = 0f
- val bottom = 550f
- val right = 100f
- placeholderViewModel.onBoundsChanged(
- left = left,
- top = top,
- right = right,
- bottom = bottom
+ val radius = MutableStateFlow(32)
+ val leftOffset = MutableStateFlow(0)
+ val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, leftOffset))
+
+ placeholderViewModel.onScrimBoundsChanged(
+ ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
)
- assertThat(clipping?.bounds)
- .isEqualTo(ShadeScrimBounds(left = left, top = top, right = right, bottom = bottom))
+ assertThat(shape)
+ .isEqualTo(
+ ShadeScrimShape(
+ bounds =
+ ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
+ topRadius = 32,
+ bottomRadius = 0
+ )
+ )
+
+ leftOffset.value = 200
+ radius.value = 24
+ placeholderViewModel.onScrimBoundsChanged(
+ ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
+ )
+ assertThat(shape)
+ .isEqualTo(
+ ShadeScrimShape(
+ bounds =
+ ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
+ topRadius = 24,
+ bottomRadius = 0
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index dc928c49fbd3..50b77dcf9468 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -68,11 +68,11 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
kosmos.shadeRepository.setShadeMode(ShadeMode.Single)
assertThat(stackRounding)
- .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = false))
+ .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = false))
kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
assertThat(stackRounding)
- .isEqualTo(ShadeScrimRounding(roundTop = true, roundBottom = true))
+ .isEqualTo(ShadeScrimRounding(isTopRounded = true, isBottomRounded = true))
}
@Test(expected = IllegalStateException::class)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index d4a704919a49..1f0812da9fe9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -16,14 +16,10 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -40,23 +36,21 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val underTest = kosmos.notificationsPlaceholderViewModel
@Test
- @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- fun onBoundsChanged_setsNotificationContainerBounds() =
+ fun onBoundsChanged() =
kosmos.testScope.runTest {
- underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
- val containerBounds by
- collectLastValue(kosmos.keyguardInteractor.notificationContainerBounds)
+ val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
+ underTest.onScrimBoundsChanged(bounds)
val stackBounds by
collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
- assertThat(containerBounds)
- .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
- assertThat(stackBounds)
- .isEqualTo(ShadeScrimBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ assertThat(stackBounds).isEqualTo(bounds)
}
@Test
- fun onContentTopChanged_setsContentTop() {
- underTest.onContentTopChanged(padding = 5f)
- assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f)
- }
+ fun onStackBoundsChanged() =
+ kosmos.testScope.runTest {
+ underTest.onStackBoundsChanged(top = 5f, bottom = 500f)
+ assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f)
+ assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value)
+ .isEqualTo(500f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 509a82da8eb9..ac8387f28e6c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,14 +19,17 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -136,12 +139,12 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged off value)
- assertThat(dimens!!.paddingTop).isEqualTo(10)
+ assertThat(paddingTop).isEqualTo(10)
}
@Test
@@ -154,11 +157,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged on value)
- assertThat(dimens!!.paddingTop).isEqualTo(5)
+ assertThat(paddingTop).isEqualTo(5)
}
@Test
@@ -168,11 +171,11 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
- assertThat(dimens!!.paddingTop).isEqualTo(0)
+ assertThat(paddingTop).isEqualTo(0)
}
@Test
@@ -200,9 +203,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val headerResourceHeight = 50
val headerHelperHeight = 100
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -219,6 +222,27 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ @EnableSceneContainer
+ fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
testScope.runTest {
mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@@ -238,6 +262,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
fun glanceableHubAlpha_lockscreenToHub() =
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -661,6 +705,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun translationYUpdatesOnKeyguard() =
testScope.runTest {
val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 30564bb6eb84..29f286fd31fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -47,12 +48,14 @@ import org.mockito.junit.MockitoRule
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
class AvalancheControllerTest : SysuiTestCase() {
- private val mAvalancheController = AvalancheController()
-
// For creating mocks
@get:Rule var rule: MockitoRule = MockitoJUnit.rule()
@Mock private val runnableMock: Runnable? = null
+ // For creating AvalancheController
+ @Mock private lateinit var dumpManager: DumpManager
+ private lateinit var mAvalancheController: AvalancheController
+
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
@@ -73,7 +76,10 @@ class AvalancheControllerTest : SysuiTestCase() {
)
.then { i: InvocationOnMock -> i.getArgument(0) }
- // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
+ // declaration, where mocks are null
+ mAvalancheController = AvalancheController(dumpManager)
+
testableHeadsUpManager =
TestableHeadsUpManager(
mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 3dc449514699..7c130be7849e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -73,7 +74,9 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
- private AvalancheController mAvalancheController = new AvalancheController();
+
+ @Mock private DumpManager dumpManager;
+ private AvalancheController mAvalancheController;
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -130,6 +133,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
public void SysuiSetup() throws Exception {
super.SysuiSetup();
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+ mAvalancheController = new AvalancheController(dumpManager);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 61a79d897b0b..a8a75c052000 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -32,6 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -76,7 +77,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
- private AvalancheController mAvalancheController = new AvalancheController();
+ @Mock private DumpManager dumpManager;
+ private AvalancheController mAvalancheController;
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -154,6 +156,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
mDependency.injectMockDependency(NotificationShadeWindowController.class);
mContext.getOrCreateTestableResources().addOverride(
R.integer.ambient_notification_extension_time, 500);
+
+ mAvalancheController = new AvalancheController(dumpManager);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 6f7f20b47199..462f36d73138 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -62,7 +61,6 @@ class MediaOutputViewModelTest : SysuiTestCase() {
MediaOutputViewModel(
applicationContext,
testScope.backgroundScope,
- volumePanelViewModel,
mediaOutputActionsInteractor,
mediaDeviceSessionInteractor,
mediaOutputInteractor,
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 173d57b335f0..3b6b5a0db393 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -91,6 +91,7 @@
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
android:gravity="center_horizontal"
+ android:layout_gravity="center"
android:imeOptions="flagForceAscii|actionDone"
android:inputType="textPassword"
android:maxLength="500"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 909d4fca5018..5aac65396532 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -55,6 +55,7 @@
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
android:gravity="center"
+ android:layout_gravity="center"
android:singleLine="true"
android:textStyle="normal"
android:inputType="textPassword"
diff --git a/packages/SystemUI/res/anim/slide_in_up.xml b/packages/SystemUI/res/anim/slide_in_up.xml
new file mode 100644
index 000000000000..6089a2809de3
--- /dev/null
+++ b/packages/SystemUI/res/anim/slide_in_up.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="100%p"
+ android:toYDelta="0"
+ android:duration="@android:integer/config_shortAnimTime" />
diff --git a/packages/SystemUI/res/anim/slide_out_down.xml b/packages/SystemUI/res/anim/slide_out_down.xml
new file mode 100644
index 000000000000..5a7b591fce9e
--- /dev/null
+++ b/packages/SystemUI/res/anim/slide_out_down.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="0"
+ android:toYDelta="100%p"
+ android:duration="@android:integer/config_shortAnimTime" />
diff --git a/packages/SystemUI/res/color/menu_item_text.xml b/packages/SystemUI/res/color/menu_item_text.xml
new file mode 100644
index 000000000000..0d05650b8082
--- /dev/null
+++ b/packages/SystemUI/res/color/menu_item_text.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <item android:state_enabled="false"
+ android:color="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0.38" />
+
+ <item android:color="?androidprv:attr/materialColorOnSurface"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
new file mode 100644
index 000000000000..ed1c6c723543
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,14h4v2h-4z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,10h4v2h-4z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_finder_active.xml b/packages/SystemUI/res/drawable/ic_finder_active.xml
index 8ca221ab7392..2423e341ec35 100644
--- a/packages/SystemUI/res/drawable/ic_finder_active.xml
+++ b/packages/SystemUI/res/drawable/ic_finder_active.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
@@ -10,5 +11,6 @@
<path
android:pathData="M12.797,4.005C11.949,3.936 11.203,4.597 11.203,5.467V6.659C8.855,7.001 6.998,8.856 6.653,11.203H5.467C4.597,11.203 3.936,11.948 4.005,12.796L4.006,12.802L4.006,12.809C4.38,16.605 7.399,19.625 11.195,20C12.051,20.087 12.803,19.404 12.803,18.547V17.355C15.154,17.012 17.013,15.154 17.355,12.803H18.54C19.406,12.803 20.079,12.058 19.992,11.196C19.618,7.4 16.606,4.388 12.812,4.006L12.804,4.006L12.797,4.005ZM11.203,9.344V8.283C9.741,8.591 8.588,9.741 8.278,11.203H9.344C9.585,10.4 10.179,9.754 10.942,9.437C11.027,9.402 11.114,9.371 11.203,9.344ZM11.998,13.171C11.358,13.175 10.828,12.651 10.827,12.004H10.827C10.827,11.959 10.83,11.915 10.835,11.871C10.885,11.427 11.185,11.056 11.59,10.902C11.694,10.863 11.806,10.838 11.921,10.83C11.948,10.833 11.976,10.834 12.003,10.834C12.65,10.834 13.177,11.356 13.179,12.007C13.177,12.622 12.695,13.13 12.091,13.175C12.06,13.172 12.029,13.17 11.998,13.171ZM17.353,11.203H18.383C18.028,8.289 15.72,5.979 12.804,5.616V6.658C15.153,7 17.004,8.852 17.353,11.203ZM14.663,11.203C14.395,10.311 13.692,9.611 12.804,9.344V8.283C14.265,8.59 15.414,9.736 15.727,11.203H14.663ZM5.615,12.803H6.654C7.001,15.15 8.855,17.002 11.203,17.346V18.391C8.287,18.034 5.972,15.719 5.615,12.803ZM11.203,14.666C10.316,14.394 9.613,13.692 9.345,12.803H8.279C8.591,14.264 9.741,15.412 11.203,15.721V14.666ZM14.661,12.811H15.729C15.418,14.272 14.266,15.422 12.804,15.73V14.662C13.689,14.396 14.391,13.699 14.661,12.811Z"
android:fillColor="#ffffff"
- android:fillType="evenOdd"/>
+ android:fillType="evenOdd"
+ tools:ignore="VectorPath" />
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
index 1b12e74141a7..0406f0e4304e 100644
--- a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
+++ b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
@@ -14,12 +14,15 @@
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="48"
android:viewportHeight="48"
- android:tint="?android:attr/textColorSecondary">
+ android:tint="?androidprv:attr/materialColorOnSurfaceVariant">
<path
android:fillColor="@android:color/white"
+ android:strokeColor="@android:color/white"
+ android:strokeWidth="2"
android:pathData="M39.8,41.95 L26.65,28.8Q25.15,30.1 23.15,30.825Q21.15,31.55 18.9,31.55Q13.5,31.55 9.75,27.8Q6,24.05 6,18.75Q6,13.45 9.75,9.7Q13.5,5.95 18.85,5.95Q24.15,5.95 27.875,9.7Q31.6,13.45 31.6,18.75Q31.6,20.9 30.9,22.9Q30.2,24.9 28.8,26.65L42,39.75ZM18.85,28.55Q22.9,28.55 25.75,25.675Q28.6,22.8 28.6,18.75Q28.6,14.7 25.75,11.825Q22.9,8.95 18.85,8.95Q14.75,8.95 11.875,11.825Q9,14.7 9,18.75Q9,22.8 11.875,25.675Q14.75,28.55 18.85,28.55Z"/>
-</vector> \ No newline at end of file
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
index 9a022960d1e3..321a04a1fb5e 100644
--- a/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
+++ b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
@@ -17,5 +17,5 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/screenrecord_spinner_background_radius"/>
- <solid android:color="?androidprv:attr/colorAccentSecondary" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/shortcut_button_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
index bf908532ac17..2e2d9b9a3e35 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="16dp"/>
+ <corners android:radius="@dimen/ksh_button_corner_radius"/>
<solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
index f692ed975319..5b88bb922a9e 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="16dp"/>
+ <corners android:radius="@dimen/ksh_button_corner_radius"/>
<solid android:color="?androidprv:attr/materialColorPrimary"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
index 6ce3eaecc147..aa0b2689f5bf 100644
--- a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
@@ -17,8 +17,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorBackground"/>
- <corners android:topLeftRadius="16dp"
- android:topRightRadius="16dp"
+ <corners android:topLeftRadius="@dimen/ksh_dialog_top_corner_radius"
+ android:topRightRadius="@dimen/ksh_dialog_top_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
-</shape> \ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/drawable/shortcut_search_background.xml b/packages/SystemUI/res/drawable/shortcut_search_background.xml
index 66fc191cb736..d6847f0abb8d 100644
--- a/packages/SystemUI/res/drawable/shortcut_search_background.xml
+++ b/packages/SystemUI/res/drawable/shortcut_search_background.xml
@@ -19,8 +19,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
- <corners android:radius="24dp" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
+ <corners android:radius="@dimen/ksh_search_box_corner_radius" />
</shape>
</item>
-</layer-list> \ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
index 6c4d4fbcc1fe..2675906580f1 100644
--- a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
+++ b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
@@ -20,7 +20,7 @@
<shape android:shape="oval">
<size android:width="24dp"
android:height="24dp" />
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 1777bdf92786..dabfe9df6d2e 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -39,7 +39,6 @@ android:layout_height="match_parent">
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
@@ -60,11 +59,12 @@ android:layout_height="match_parent">
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
- android:fillViewport="true"
- android:padding="24dp"
- app:layout_constrainedHeight="true"
- app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ android:paddingBottom="16dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="12dp"
+ android:paddingTop="24dp"
+ android:fadeScrollbars="false"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toStartOf="@+id/midGuideline"
app:layout_constraintStart_toStartOf="@id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline">
@@ -91,7 +91,7 @@ android:layout_height="match_parent">
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:paddingLeft="8dp"
+ android:paddingLeft="16dp"
app:layout_constraintBottom_toBottomOf="@+id/logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
@@ -119,7 +119,7 @@ android:layout_height="match_parent">
style="@style/TextAppearance.AuthCredential.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
+ android:layout_marginTop="16dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -133,6 +133,7 @@ android:layout_height="match_parent">
android:id="@+id/customized_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone"
@@ -148,6 +149,7 @@ android:layout_height="match_parent">
style="@style/TextAppearance.AuthCredential.Description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -179,7 +181,7 @@ android:layout_height="match_parent">
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -189,7 +191,7 @@ android:layout_height="match_parent">
android:id="@+id/button_bar"
layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
app:layout_constraintEnd_toEndOf="@id/scrollView"
app:layout_constraintStart_toStartOf="@id/scrollView"
@@ -204,14 +206,6 @@ android:layout_height="match_parent">
app:barrierDirection="top"
app:constraint_referenced_ids="scrollView" />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
-
<androidx.constraintlayout.widget.Guideline
android:id="@+id/leftGuideline"
android:layout_width="wrap_content"
@@ -238,13 +232,13 @@ android:layout_height="match_parent">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_begin="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 8b886a7fdffb..240ababc7da4 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -29,28 +29,31 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
app:layout_constraintStart_toStartOf="@+id/leftGuideline"
- app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+ app:layout_constraintTop_toTopOf="@+id/topBarrier"
+ app:layout_constraintWidth_max="640dp" />
<include
- layout="@layout/biometric_prompt_button_bar"
android:id="@+id/button_bar"
+ layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="40dp"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/panel"
- app:layout_constraintStart_toStartOf="@id/panel"/>
+ app:layout_constraintStart_toStartOf="@id/panel" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
android:fillViewport="true"
- android:paddingBottom="36dp"
- android:paddingHorizontal="24dp"
+ android:paddingBottom="32dp"
+ android:paddingHorizontal="32dp"
android:paddingTop="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
app:layout_constraintEnd_toEndOf="@id/panel"
app:layout_constraintStart_toStartOf="@id/panel"
app:layout_constraintTop_toTopOf="@+id/topGuideline"
@@ -63,10 +66,10 @@
<ImageView
android:id="@+id/logo"
- android:contentDescription="@string/biometric_dialog_logo"
android:layout_width="@dimen/biometric_prompt_logo_size"
android:layout_height="@dimen/biometric_prompt_logo_size"
android:layout_gravity="center"
+ android:contentDescription="@string/biometric_dialog_logo"
android:scaleType="fitXY"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/logo_description"
@@ -79,6 +82,7 @@
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -114,6 +118,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
+ android:paddingTop="24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -126,6 +131,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
+ android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -153,7 +160,7 @@
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/panel"
app:layout_constraintStart_toStartOf="@+id/panel"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -172,12 +179,12 @@
<!-- Try Again Button -->
<androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
+ android:id="@+id/scrollBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
+ app:constraint_referenced_ids="biometric_icon, button_bar" />
<!-- Guidelines for setting panel border -->
<androidx.constraintlayout.widget.Guideline
@@ -199,14 +206,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.25171" />
+ app:layout_constraintGuide_begin="56dp" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
@@ -216,7 +223,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.8"
+ app:layout_constraintVertical_bias="1.0"
tools:srcCompat="@tools:sample/avatars" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 810c7433e4ad..4d2310a2a6ca 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -17,12 +17,13 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:theme="@style/Theme.SystemUI.Dialog"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Negative Button, reserved for app -->
<Button
android:id="@+id/button_negative"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -36,7 +37,7 @@
<!-- Cancel Button, replaces negative button when biometric is accepted -->
<Button
android:id="@+id/button_cancel"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -49,7 +50,7 @@
<!-- "Use Credential" Button, replaces if device credential is allowed -->
<Button
android:id="@+id/button_use_credential"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -61,7 +62,7 @@
<!-- Positive Button -->
<Button
android:id="@+id/button_confirm"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -76,7 +77,7 @@
<!-- Try Again Button -->
<Button
android:id="@+id/button_try_again"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 74bf318465b6..8e3cf4d9b446 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -35,24 +35,26 @@
android:id="@+id/button_bar"
layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="40dp"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/panel"
app:layout_constraintStart_toStartOf="@id/panel" />
<ScrollView
android:id="@+id/scrollView"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:fillViewport="true"
+ android:fadeScrollbars="false"
android:paddingBottom="36dp"
android:paddingHorizontal="24dp"
android:paddingTop="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
- app:layout_constraintEnd_toEndOf="@id/rightGuideline"
- app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
+ app:layout_constraintEnd_toEndOf="@id/panel"
+ app:layout_constraintStart_toStartOf="@id/panel"
app:layout_constraintTop_toTopOf="@+id/topGuideline"
app:layout_constraintVertical_bias="1.0">
@@ -68,6 +70,7 @@
android:layout_height="@dimen/biometric_prompt_logo_size"
android:layout_gravity="center"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@+id/logo_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -79,6 +82,7 @@
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -114,8 +118,8 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
- android:visibility="gone"
android:paddingTop="24dp"
+ android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -127,7 +131,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
- android:paddingTop="24dp"
+ android:textAlignment="viewStart"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -154,7 +159,7 @@
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/panel"
app:layout_constraintStart_toStartOf="@+id/panel"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -169,12 +174,12 @@
app:constraint_referenced_ids="scrollView" />
<androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
+ android:id="@+id/scrollBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
+ app:constraint_referenced_ids="biometric_icon, button_bar" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/leftGuideline"
@@ -195,14 +200,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.25" />
+ app:layout_constraintGuide_begin="119dp" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
@@ -212,7 +217,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.8"
+ app:layout_constraintVertical_bias="1.0"
tools:srcCompat="@tools:sample/avatars" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
index ae243130e537..14b3b55df0a4 100644
--- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/editor_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index a0051008ddd6..5ab23271922c 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -15,13 +15,14 @@
~ limitations under the License
-->
<com.android.systemui.statusbar.KeyboardShortcutAppItemLayout
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:background="@drawable/list_item_background"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
+ android:minHeight="@dimen/ksh_app_item_minimum_height"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/keyboard_shortcuts_icon"
@@ -39,7 +40,8 @@
android:layout_height="wrap_content"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp"
android:maxLines="5"
android:singleLine="false"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
index 530e46eb1c40..76e5b12bdcd6 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
@@ -15,6 +15,6 @@
limitations under the License
-->
<View xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="0dp"
+ android:layout_marginTop="@dimen/ksh_category_separator_margin"
+ android:layout_marginBottom="@dimen/ksh_category_separator_margin"
style="@style/ShortcutHorizontalDivider" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
index 4f100f6b94d1..6e7fde68ca04 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
@@ -16,10 +16,12 @@
~ limitations under the License
-->
<TextView 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="match_parent"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
- android:fontFamily="sans-serif-medium"
+ android:textColor="?androidprv:attr/materialColorPrimary"
android:importantForAccessibility="yes"
android:paddingTop="20dp"
android:paddingBottom="10dp"/>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index f6042e467987..2cfd644e9d62 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -16,15 +16,21 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:background="@drawable/shortcut_dialog_bg"
android:layout_width="@dimen/ksh_layout_width"
android:layout_height="wrap_content"
android:orientation="vertical">
+
+ <com.google.android.material.bottomsheet.BottomSheetDragHandleView
+ android:id="@+id/drag_handle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
<TextView
android:id="@+id/shortcut_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="40dp"
android:layout_gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorPrimary"
@@ -39,44 +45,47 @@
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:padding="16dp"
android:background="@drawable/shortcut_search_background"
android:drawableStart="@drawable/ic_shortcutlist_search"
android:drawablePadding="15dp"
android:singleLine="true"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:inputType="text"
android:textDirection="locale"
android:textAlignment="viewStart"
android:hint="@string/keyboard_shortcut_search_list_hint"
- android:textColorHint="?android:attr/textColorTertiary" />
+ android:textAppearance="@android:style/TextAppearance.Material"
+ android:textSize="16sp"
+ android:textColorHint="?androidprv:attr/materialColorOutline" />
<ImageButton
android:id="@+id/keyboard_shortcuts_search_cancel"
android:layout_gravity="center_vertical|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="49dp"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:padding="16dp"
android:contentDescription="@string/keyboard_shortcut_clear_text"
android:src="@drawable/ic_shortcutlist_search_button_cancel"
android:background="@drawable/shortcut_search_cancel_button"
style="@android:style/Widget.Material.Button.Borderless.Small"
- android:pointerIcon="arrow" />
+ android:pointerIcon="arrow"
+ android:visibility="gone" />
</FrameLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
android:layout_marginEnd="0dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
+ android:layout_gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/shortcut_system"
@@ -113,29 +122,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:layout_gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/keyboard_shortcut_search_list_no_result"/>
- <ScrollView
+ <androidx.core.widget.NestedScrollView
android:id="@+id/keyboard_shortcuts_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:overScrollMode="never"
- android:layout_marginBottom="16dp"
+ android:clipToPadding="false"
android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
- </ScrollView>
+ </androidx.core.widget.NestedScrollView>
<!-- Required for stretching to full available height when the items in the scroll view
occupy less space then the full height -->
<View
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
index 53ad9f157a2e..30d7b0ae739a 100644
--- a/packages/SystemUI/res/layout/record_issue_dialog.xml
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -54,6 +54,7 @@
android:layout_weight="0"
android:src="@drawable/ic_screenrecord"
app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/screenrecord_option_padding" />
@@ -78,4 +79,44 @@
android:layout_weight="0"
android:contentDescription="@string/quick_settings_screen_record_label" />
</LinearLayout>
+
+ <!-- Bug Report Switch -->
+ <LinearLayout
+ android:id="@+id/bugreport_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_bugreport"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center"
+ android:text="@string/qs_record_issue_bug_report"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no"/>
+
+ <Switch
+ android:id="@+id/bugreport_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0"
+ android:contentDescription="@string/qs_record_issue_bug_report" />
+ </LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
index 1e5b249b5021..8c31713a5e34 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
@@ -30,7 +30,7 @@
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?androidprv:attr/textColorOnAccent" />
+ android:textColor="@color/menu_item_text" />
<TextView
android:id="@android:id/text2"
@@ -39,6 +39,6 @@
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?androidprv:attr/colorError" />
+ android:textColor="?androidprv:attr/materialColorError" />
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index c988b4afcf74..eeb64bd8460e 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -51,15 +51,7 @@
<LinearLayout
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_share_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_edit_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_scroll_chip"
- android:visibility="gone" />
- </LinearLayout>
+ android:layout_height="wrap_content" />
</HorizontalScrollView>
<View
android:id="@+id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 55606aa0bc82..56ebc0668097 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -94,4 +94,7 @@
<dimen name="keyguard_indication_margin_bottom">8dp</dimen>
<dimen name="lock_icon_margin_bottom">24dp</dimen>
+
+ <!-- Keyboard shortcuts helper -->
+ <dimen name="ksh_container_horizontal_margin">48dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 1e54fc9e1445..2cfba01fe1c8 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -110,4 +110,6 @@
<dimen name="controls_content_padding">24dp</dimen>
<dimen name="control_list_vertical_spacing">8dp</dimen>
<dimen name="control_list_horizontal_spacing">16dp</dimen>
+ <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe8f2fff550a..b0b548295b71 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -978,6 +978,7 @@
<dimen name="assist_disclosure_shadow_thickness">1.5dp</dimen>
<!-- Keyboard shortcuts helper -->
+ <dimen name="ksh_container_horizontal_margin">32dp</dimen>
<dimen name="ksh_layout_width">@dimen/match_parent</dimen>
<dimen name="ksh_item_text_size">14sp</dimen>
<dimen name="ksh_item_padding">0dp</dimen>
@@ -985,6 +986,11 @@
<dimen name="ksh_icon_scaled_size">18dp</dimen>
<dimen name="ksh_key_view_padding_vertical">4dp</dimen>
<dimen name="ksh_key_view_padding_horizontal">12dp</dimen>
+ <dimen name="ksh_button_corner_radius">12dp</dimen>
+ <dimen name="ksh_dialog_top_corner_radius">28dp</dimen>
+ <dimen name="ksh_search_box_corner_radius">100dp</dimen>
+ <dimen name="ksh_app_item_minimum_height">64dp</dimen>
+ <dimen name="ksh_category_separator_margin">16dp</dimen>
<!-- The size of corner radius of the arrow in the onboarding toast. -->
<dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen>
@@ -1085,7 +1091,7 @@
<dimen name="remote_input_history_extra_height">60dp</dimen>
<!-- Biometric Dialog values -->
- <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_face_icon_size">54dp</dimen>
<dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
<dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
@@ -1103,6 +1109,22 @@
<dimen name="biometric_dialog_width">240dp</dimen>
<dimen name="biometric_dialog_height">240dp</dimen>
+ <!-- Dimensions for biometric prompt panel padding -->
+ <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen>
+ <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen>
+ <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen>
+ <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen>
+ <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen>
+
+ <!-- Dimensions for biometric prompt icon padding -->
+ <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen>
+ <dimen name="biometric_prompt_portrait_medium_bottom_padding">160dp</dimen>
+ <dimen name="biometric_prompt_portrait_large_screen_bottom_padding">176dp</dimen>
+ <dimen name="biometric_prompt_landscape_small_bottom_padding">192dp</dimen>
+ <dimen name="biometric_prompt_landscape_small_horizontal_padding">145dp</dimen>
+ <dimen name="biometric_prompt_landscape_medium_bottom_padding">148dp</dimen>
+ <dimen name="biometric_prompt_landscape_medium_horizontal_padding">125dp</dimen>
+
<!-- Dimensions for biometric prompt custom content view. -->
<dimen name="biometric_prompt_logo_size">32dp</dimen>
<dimen name="biometric_prompt_content_corner_radius">28dp</dimen>
@@ -1867,6 +1889,10 @@
.2
</item>
+ <item name="dream_overlay_bouncer_min_region_screen_percentage" format="float" type="dimen">
+ .05
+ </item>
+
<!-- The padding applied to the dream overlay container -->
<dimen name="dream_overlay_container_padding_start">0dp</dimen>
<dimen name="dream_overlay_container_padding_end">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 71353b6774af..af661aa172c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -872,6 +872,8 @@
<string name="qs_record_issue_start">Start</string>
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="qs_record_issue_stop">Stop</string>
+ <!-- QuickSettings: Should user take a bugreport or only share trace files [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_bug_report">Bug Report</string>
<!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
<string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
@@ -1971,7 +1973,7 @@
<!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] -->
<string name="keyboard_shortcut_clear_text">Clear search query</string>
<!-- The title for keyboard shortcut search list [CHAR LIMIT=25] -->
- <string name="keyboard_shortcut_search_list_title">Shortcuts</string>
+ <string name="keyboard_shortcut_search_list_title">Keyboard Shortcuts</string>
<!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
<!-- The description for no shortcuts results [CHAR LIMIT=25] -->
@@ -2023,12 +2025,12 @@
<!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
<string name="group_system_quick_memo">Take a note</string>
- <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
- <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string>
- <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] -->
- <string name="system_multitasking_rhs">Enter split screen with current app to RHS</string>
- <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] -->
- <string name="system_multitasking_lhs">Enter split screen with current app to LHS</string>
+ <!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_system_multitasking">Multitasking</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app on the right [CHAR LIMIT=70] -->
+ <string name="system_multitasking_rhs">Use split screen with current app on the right</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app on the left [CHAR LIMIT=70] -->
+ <string name="system_multitasking_lhs">Use split screen with current app on the left</string>
<!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
<string name="system_multitasking_full_screen">Switch from split screen to full screen</string>
<!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6462d02de481..2c9006e50f92 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -224,11 +224,14 @@
<style name="TextAppearance.AuthCredential.ContentViewListItem" parent="TextAppearance.Material3.BodySmall">
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
<item name="android:paddingTop">@dimen/biometric_prompt_content_list_item_padding_top</item>
+ <item name="android:breakStrategy">high_quality</item>
</style>
<style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium">
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:marqueeRepeatLimit">marquee_forever</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">marquee</item>
</style>
<style name="TextAppearance.AuthCredential.Error">
@@ -365,6 +368,21 @@
<item name="android:layout_height">wrap_content</item>
</style>
+ <style name="KeyboardShortcutHelper" parent="@android:style/Theme.DeviceDefault.Settings">
+ <!-- Needed to be able to use BottomSheetDragHandleView -->
+ <item name="android:windowActionBar">false</item>
+ <item name="bottomSheetDragHandleStyle">@style/KeyboardShortcutHelper.BottomSheet.DragHandle</item>
+ </style>
+
+ <style name="KeyboardShortcutHelper.BottomSheet.DragHandle" parent="Widget.Material3.BottomSheet.DragHandle">
+ <item name="tint">?androidprv:attr/materialColorOutlineVariant</item>
+ </style>
+
+ <style name="KeyboardShortcutHelper.BottomSheetDialogAnimation">
+ <item name="android:windowEnterAnimation">@anim/slide_in_up</item>
+ <item name="android:windowExitAnimation">@anim/slide_out_down</item>
+ </style>
+
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer" />
<style name="Animation" />
@@ -1597,14 +1615,15 @@
<item name="android:layout_marginEnd">12dp</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
- <item name="android:minHeight">40dp</item>
+ <item name="android:minHeight">36dp</item>
+ <item name="android:minWidth">120dp</item>
<item name="android:stateListAnimator">@*android:anim/flat_button_state_list_anim_material</item>
<item name="android:pointerIcon">arrow</item>
</style>
<style name="ShortcutHorizontalDivider">
- <item name="android:layout_width">120dp</item>
- <item name="android:layout_height">1dp</item>
+ <item name="android:layout_width">132dp</item>
+ <item name="android:layout_height">2dp</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:background">?android:attr/dividerHorizontal</item>
</style>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 42ba05cff906..fbe139930e11 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -58,7 +58,7 @@ android_library {
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
"WindowManager-Shell-shared",
- "tracinglib-platform",
+ "//frameworks/libs/systemui:tracinglib-platform",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
@@ -68,7 +68,7 @@ android_library {
"kotlinx_coroutines",
"dagger2",
"jsr330",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
index 9574fba62269..829dc4ff69f8 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
@@ -54,8 +54,9 @@ public class UdfpsUtils {
}
/**
- * Gets the touch in native coordinates. Map the touch to portrait mode if the device is in
- * landscape mode.
+ * Gets the touch in native coordinates.
+ *
+ * Maps the touch to portrait mode if the device is in landscape mode.
*
* @param idx The pointer identifier.
* @param event The MotionEvent object containing full information about the event.
@@ -64,35 +65,87 @@ public class UdfpsUtils {
*/
public Point getTouchInNativeCoordinates(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
- Point portraitTouch = getPortraitTouch(idx, event, udfpsOverlayParams);
+ return getTouchInNativeCoordinates(idx, event, udfpsOverlayParams, true);
+ }
+
+ /**
+ * Gets the touch in native coordinates.
+ *
+ * Optionally map the touch to portrait mode if the device is in landscape mode.
+ *
+ * @param idx The pointer identifier.
+ * @param event The MotionEvent object containing full information about the event.
+ * @param udfpsOverlayParams The [UdfpsOverlayParams] used.
+ * @param rotateToPortrait Whether to rotate the touch to portrait orientation.
+ * @return The mapped touch event.
+ */
+ public Point getTouchInNativeCoordinates(int idx, MotionEvent event,
+ UdfpsOverlayParams udfpsOverlayParams, boolean rotateToPortrait) {
+ Point touch;
+ if (rotateToPortrait) {
+ touch = getPortraitTouch(idx, event, udfpsOverlayParams);
+ } else {
+ touch = new Point((int) event.getRawX(idx), (int) event.getRawY(idx));
+ }
// Scale the coordinates to native resolution.
float scale = udfpsOverlayParams.getScaleFactor();
- portraitTouch.x = (int) (portraitTouch.x / scale);
- portraitTouch.y = (int) (portraitTouch.y / scale);
- return portraitTouch;
+ touch.x = (int) (touch.x / scale);
+ touch.y = (int) (touch.y / scale);
+ return touch;
}
/**
* @param idx The pointer identifier.
* @param event The MotionEvent object containing full information about the event.
* @param udfpsOverlayParams The [UdfpsOverlayParams] used.
- * @return Whether the touch event is within sensor area.
+ * @return Whether the touch event (that needs to be rotated to portrait) is within sensor area.
*/
public boolean isWithinSensorArea(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
- Point portraitTouch = getPortraitTouch(idx, event, udfpsOverlayParams);
- return udfpsOverlayParams.getSensorBounds().contains(portraitTouch.x, portraitTouch.y);
+ return isWithinSensorArea(idx, event, udfpsOverlayParams, true);
+ }
+
+ /**
+ * @param idx The pointer identifier.
+ * @param event The MotionEvent object containing full information about the event.
+ * @param udfpsOverlayParams The [UdfpsOverlayParams] used.
+ * @param rotateTouchToPortrait Whether to rotate the touch coordinates to portrait.
+ * @return Whether the touch event is within sensor area.
+ */
+ public boolean isWithinSensorArea(int idx, MotionEvent event,
+ UdfpsOverlayParams udfpsOverlayParams, boolean rotateTouchToPortrait) {
+ Point touch;
+ if (rotateTouchToPortrait) {
+ touch = getPortraitTouch(idx, event, udfpsOverlayParams);
+ } else {
+ touch = new Point((int) event.getRawX(idx), (int) event.getRawY(idx));
+ }
+ return udfpsOverlayParams.getSensorBounds().contains(touch.x, touch.y);
+ }
+
+ /**
+ * This function computes the angle of touch relative to the sensor, rotated to portrait,
+ * and maps the angle to a list of help messages which are announced if accessibility is
+ * enabled.
+ *
+ * @return announcement string
+ */
+ public String onTouchOutsideOfSensorArea(boolean touchExplorationEnabled, Context context,
+ int scaledTouchX, int scaledTouchY, UdfpsOverlayParams udfpsOverlayParams) {
+ return onTouchOutsideOfSensorArea(touchExplorationEnabled, context, scaledTouchX,
+ scaledTouchY, udfpsOverlayParams, true);
}
/**
* This function computes the angle of touch relative to the sensor and maps the angle to a list
* of help messages which are announced if accessibility is enabled.
*
- * @return Whether the announcing string is null
+ * @return announcement string
*/
public String onTouchOutsideOfSensorArea(boolean touchExplorationEnabled, Context context,
- int scaledTouchX, int scaledTouchY, UdfpsOverlayParams udfpsOverlayParams) {
+ int scaledTouchX, int scaledTouchY, UdfpsOverlayParams udfpsOverlayParams,
+ boolean touchRotatedToPortrait) {
if (!touchExplorationEnabled) {
return null;
}
@@ -116,7 +169,8 @@ public class UdfpsUtils {
scaledTouchY,
scaledSensorX,
scaledSensorY,
- udfpsOverlayParams.getRotation()
+ udfpsOverlayParams.getRotation(),
+ touchRotatedToPortrait
);
Log.v(TAG, "Announcing touch outside : $theStr");
return theStr;
@@ -132,7 +186,7 @@ public class UdfpsUtils {
* touchHints[1] = "Move Fingerprint down" And so on.
*/
private String onTouchOutsideOfSensorAreaImpl(String[] touchHints, float touchX,
- float touchY, float sensorX, float sensorY, int rotation) {
+ float touchY, float sensorX, float sensorY, int rotation, boolean rotatedToPortrait) {
float xRelativeToSensor = touchX - sensorX;
// Touch coordinates are with respect to the upper left corner, so reverse
// this calculation
@@ -153,13 +207,16 @@ public class UdfpsUtils {
int index = (int) ((degrees + halfBucketDegrees) % 360 / degreesPerBucket);
index %= touchHints.length;
- // A rotation of 90 degrees corresponds to increasing the index by 1.
- if (rotation == Surface.ROTATION_90) {
- index = (index + 1) % touchHints.length;
- }
- if (rotation == Surface.ROTATION_270) {
- index = (index + 3) % touchHints.length;
+ if (rotatedToPortrait) {
+ // A rotation of 90 degrees corresponds to increasing the index by 1.
+ if (rotation == Surface.ROTATION_90) {
+ index = (index + 1) % touchHints.length;
+ }
+ if (rotation == Surface.ROTATION_270) {
+ index = (index + 3) % touchHints.length;
+ }
}
+
return touchHints[index];
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 46329148a659..dcc14409f046 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -155,5 +155,10 @@ interface ISystemUiProxy {
*/
oneway void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) = 54;
- // Next id = 55
+ /**
+ * Set the override value for home button long press duration in ms and slop multiplier.
+ */
+ oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) = 55;
+
+ // Next id = 56
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 7af991743457..d0d5caf57ffc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -32,6 +32,8 @@ import dagger.Module
import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.android.asCoroutineDispatcher
/**
* Dagger module with system-only dependencies for the unfold animation. The code that is used to
@@ -78,6 +80,13 @@ abstract class SystemUnfoldSharedModule {
@Provides
@UnfoldBg
@Singleton
+ fun unfoldBgDispatcher(@UnfoldBg handler: Handler): CoroutineDispatcher {
+ return handler.asCoroutineDispatcher("@UnfoldBg Dispatcher")
+ }
+
+ @Provides
+ @UnfoldBg
+ @Singleton
fun provideBgLooper(): Looper {
return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND)
.apply { start() }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 48271dea31d8..70182c16a093 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -31,6 +31,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
@@ -65,6 +66,7 @@ import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -72,7 +74,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -90,6 +91,7 @@ constructor(
@DisplaySpecific private val resources: Resources,
private val context: Context,
@Main private val mainExecutor: DelayableExecutor,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
@Background private val bgExecutor: Executor,
private val clockBuffers: ClockMessageBuffers,
private val featureFlags: FeatureFlagsClassic,
@@ -424,7 +426,7 @@ constructor(
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
zenModeController.addCallback(zenModeCallback)
disposableHandle =
- parent.repeatWhenAttached {
+ parent.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
if (MigrateClocksToBlueprint.isEnabled) {
@@ -529,18 +531,22 @@ constructor(
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
- return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
+ return scope.launch("$TAG#listenForDozeAmount") {
+ keyguardInteractor.dozeAmount.collect { handleDoze(it) }
+ }
}
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
- return scope.launch {
+ return scope.launch("$TAG#listenForDozeAmountTransition") {
merge(
keyguardTransitionInteractor.aodToLockscreenTransition.map { step ->
step.copy(value = 1f - step.value)
},
keyguardTransitionInteractor.lockscreenToAodTransition,
- )
+ ).filter {
+ it.transitionState != TransitionState.FINISHED
+ }
.collect { handleDoze(it.value) }
}
}
@@ -550,7 +556,7 @@ constructor(
*/
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
- return scope.launch {
+ return scope.launch("$TAG#listenForAnyStateToAodTransition") {
keyguardTransitionInteractor
.transitionStepsToState(AOD)
.filter { it.transitionState == TransitionState.STARTED }
@@ -561,7 +567,7 @@ constructor(
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
- return scope.launch {
+ return scope.launch("$TAG#listenForDozing") {
combine(
keyguardInteractor.dozeAmount,
keyguardInteractor.isDozing,
@@ -626,7 +632,7 @@ constructor(
}
companion object {
- private val TAG = ClockEventController::class.simpleName!!
- private val DOZE_TICKRATE_THRESHOLD = 0.99f
+ private const val TAG = "ClockEventController"
+ private const val DOZE_TICKRATE_THRESHOLD = 0.99f
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5b8eb9d3da82..790a8434c229 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -250,7 +250,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
}
-
if (!mOnlyClock) {
mDumpManager.unregisterDumpable(getClass().getSimpleName()); // unregister previous
mDumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -282,7 +281,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
protected void onViewAttached() {
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
- mClockEventController.registerListeners(mView);
+ if (!MigrateClocksToBlueprint.isEnabled()) {
+ mClockEventController.registerListeners(mView);
+ }
mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mKeyguardLargeClockTopMargin =
@@ -365,7 +366,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
@Override
protected void onViewDetached() {
mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
- mClockEventController.unregisterListeners();
+ if (!MigrateClocksToBlueprint.isEnabled()) {
+ mClockEventController.unregisterListeners();
+ }
setClock(null);
mBgExecutor.execute(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index ea8fe59d3c89..fb88f0e4e093 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -300,7 +300,7 @@ public class SystemUIApplication extends Application implements
Class<?> cls = entry.getKey();
Dependencies dep = cls.getAnnotation(Dependencies.class);
- Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ Class<?>[] deps = (dep == null ? null : dep.value());
if (deps == null || startedStartables.containsAll(Arrays.asList(deps))) {
String clsName = cls.getName();
int i = serviceIndex; // Copied to make lambda happy.
@@ -324,7 +324,7 @@ public class SystemUIApplication extends Application implements
Map.Entry<Class<?>, Provider<CoreStartable>> entry = nextQueue.removeFirst();
Class<?> cls = entry.getKey();
Dependencies dep = cls.getAnnotation(Dependencies.class);
- Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ Class<?>[] deps = (dep == null ? null : dep.value());
StringJoiner stringJoiner = new StringJoiner(", ");
for (int i = 0; deps != null && i < deps.length; i++) {
if (!startedStartables.contains(deps[i])) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 96eb4b3a4c3f..475bb2c83b0e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -43,14 +43,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ActiveHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.AvailableHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ConnectedDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.SavedHearingDeviceItemFactory;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 695d04f5df9d..737805b4d62f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,7 +27,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.res.R;
import kotlin.Pair;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 8ca083fa1e35..5df7fc9865ff 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -243,12 +243,6 @@ constructor(
}
// Authentication failed.
-
- if (tryAutoConfirm) {
- // Auto-confirm is active, the failed attempt should have no side-effects.
- return AuthenticationResult.FAILED
- }
-
repository.reportAuthenticationAttempt(isSuccessful = false)
if (authenticationResult.lockoutDurationMs > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c4d282e24a92..a667de2351d7 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -28,10 +28,14 @@ import android.os.ParcelFileDescriptor
import android.os.UserHandle
import android.util.Log
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.communal.domain.backup.CommunalPrefsBackupHelper
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
+import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManagerImpl
/**
@@ -53,6 +57,8 @@ open class BackupHelper : BackupAgentHelper() {
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
"systemui.keyguard.quickaffordance.shared_preferences"
+ private const val COMMUNAL_PREFS_BACKUP_KEY =
+ "systemui.communal.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
@@ -75,6 +81,15 @@ open class BackupHelper : BackupAgentHelper() {
userId = userHandle.identifier,
),
)
+ if (communalEnabled()) {
+ addHelper(
+ COMMUNAL_PREFS_BACKUP_KEY,
+ CommunalPrefsBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ )
+ )
+ }
}
override fun onRestoreFinished() {
@@ -100,6 +115,10 @@ open class BackupHelper : BackupAgentHelper() {
}
}
+ private fun communalEnabled(): Boolean {
+ return resources.getBoolean(R.bool.config_communalServiceEnabled) && communalHub()
+ }
+
/**
* Helper class for restoring files ONLY if they are not present.
*
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
index fd7e98f1865c..9f594feb03ab 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
@@ -30,7 +30,7 @@ enum class ColorProfile {
Active,
// Yellow for e.g., battery saver
Warning,
- // Red for e.t., low battery
+ // Red for e.g., low battery
Error,
}
@@ -108,17 +108,17 @@ sealed interface BatteryColors {
// 22% alpha white
override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+ // GM Gray 500
+ override val fill = Color.parseColor("#9AA0A6")
// GM Gray 600
- override val fill = Color.parseColor("#80868B")
- // GM Gray 700
- override val fillOnly = Color.parseColor("#5F6368")
+ override val fillOnly = Color.parseColor("#80868B")
- // GM Green 700
- override val activeFill = Color.parseColor("#188038")
+ // GM Green 500
+ override val activeFill = Color.parseColor("#34A853")
// GM Yellow 500
override val warnFill = Color.parseColor("#FBBC04")
- // GM Red 600
- override val errorFill = Color.parseColor("#D93025")
+ // GM Red 500
+ override val errorFill = Color.parseColor("#EA4335")
}
/** Color scheme appropriate for dark mode (light icons) */
@@ -132,12 +132,12 @@ sealed interface BatteryColors {
// GM Gray 400
override val fillOnly = Color.parseColor("#BDC1C6")
- // GM Green 500
- override val activeFill = Color.parseColor("#34A853")
- // GM Yellow
- override val warnFill = Color.parseColor("#FBBC04")
- // GM Red 600
- override val errorFill = Color.parseColor("#D93025")
+ // GM Green 700
+ override val activeFill = Color.parseColor("#188038")
+ // GM Yellow 700
+ override val warnFill = Color.parseColor("#F29900")
+ // GM Red 700
+ override val errorFill = Color.parseColor("#C5221F")
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2c3ebe901850..b25c3daa9407 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -400,14 +400,22 @@ public class UdfpsController implements DozeReceiver, Dumpable {
if (!mOverlayParams.equals(overlayParams)) {
mOverlayParams = overlayParams;
- final boolean wasShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState();
-
- // When the bounds change it's always necessary to re-create the overlay's window with
- // new LayoutParams. If the overlay needs to be shown, this will re-create and show the
- // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
- redrawOverlay();
- if (wasShowingAlternateBouncer) {
- mKeyguardViewManager.showBouncer(true);
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) {
+ mOverlay.updateOverlayParams(mOverlayParams);
+ } else {
+ redrawOverlay();
+ }
+ } else {
+ final boolean wasShowingAlternateBouncer =
+ mAlternateBouncerInteractor.isVisibleState();
+ // When the bounds change it's always to re-create the overlay's window with new
+ // LayoutParams. If the overlay needs to be shown, this will re-create and show the
+ // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden.
+ redrawOverlay();
+ if (wasShowingAlternateBouncer) {
+ mKeyguardViewManager.showBouncer(true);
+ }
}
}
}
@@ -850,7 +858,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mOverlay = null;
mOrientationListener.disable();
-
}
private void unconfigureDisplay(View view) {
@@ -859,7 +866,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
}
if (DeviceEntryUdfpsRefactor.isEnabled()) {
if (mUdfpsDisplayMode != null) {
- mUdfpsDisplayMode.disable(null); // beverlt
+ mUdfpsDisplayMode.disable(null);
}
} else {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 16865ca809d9..3a45db17b64c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -318,6 +318,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
addViewRunnable = null
}
+ fun updateOverlayParams(updatedOverlayParams: UdfpsOverlayParams) {
+ DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
+ overlayParams = updatedOverlayParams
+ sensorBounds = updatedOverlayParams.sensorBounds
+ getTouchOverlay()?.let {
+ windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null))
+ }
+ }
+
fun inflateUdfpsAnimation(
view: UdfpsView,
controller: UdfpsController
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 5ae2ff0e3108..3112b673d724 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.domain.interactor
import android.content.Context
+import android.graphics.Rect
import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.SensorLocation
@@ -43,6 +44,7 @@ constructor(
repository: FingerprintPropertyRepository,
configurationInteractor: ConfigurationInteractor,
displayStateInteractor: DisplayStateInteractor,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
val isUdfps: StateFlow<Boolean> =
@@ -103,4 +105,13 @@ constructor(
sensorLocation.scale = scale
sensorLocation
}
+
+ /**
+ * Sensor location for the:
+ * - current physical display
+ * - current screen resolution
+ * - device's current orientation
+ */
+ val udfpsSensorBounds: Flow<Rect> =
+ udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 88aef5675240..7ccac03bcac6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -31,18 +31,19 @@ import android.text.style.BulletSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.ViewTreeObserver
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Space
import android.widget.TextView
-import androidx.lifecycle.lifecycleScope
import com.android.settingslib.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import kotlin.math.ceil
-import kotlinx.coroutines.launch
+
+private const val TAG = "BiometricCustomizedViewBinder"
/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
object BiometricCustomizedViewBinder {
@@ -52,21 +53,20 @@ object BiometricCustomizedViewBinder {
legacyCallback: Spaghetti.Callback
) {
customizedViewContainer.repeatWhenAttached { containerView ->
- lifecycleScope.launch {
- if (contentView == null) {
- containerView.visibility = View.GONE
- return@launch
- }
+ if (contentView == null) {
+ containerView.visibility = View.GONE
+ return@repeatWhenAttached
+ }
- containerView.width { containerWidth ->
- if (containerWidth == 0) {
- return@width
- }
- (containerView as LinearLayout).addView(
- contentView.toView(containerView.context, containerWidth, legacyCallback)
- )
- containerView.visibility = View.VISIBLE
+ containerView.width { containerWidth ->
+ if (containerWidth == 0) {
+ return@width
}
+ (containerView as LinearLayout).addView(
+ contentView.toView(containerView.context, containerWidth, legacyCallback),
+ LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ )
+ containerView.visibility = View.VISIBLE
}
}
}
@@ -118,51 +118,42 @@ private fun PromptVerticalListContentView.initLayout(
containerViewWidth: Int
): View {
val inflater = LayoutInflater.from(context)
- val resources = context.resources
+ context.resources
val contentView =
inflater.inflateContentView(
R.layout.biometric_prompt_vertical_list_content_layout,
description
)
+ val listItemsToShow = ArrayList<PromptContentItem>(listItems)
// Show two column by default, once there is an item exceeding max lines, show single
// item instead.
val showTwoColumn =
- listItems.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) }
- var currRowView = createNewRowLayout(inflater)
- for (item in listItems) {
+ listItemsToShow.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) }
+ // If should show two columns and there are more than one items, make listItems always have odd
+ // number items.
+ if (showTwoColumn && listItemsToShow.size > 1 && listItemsToShow.size % 2 == 1) {
+ listItemsToShow.add(dummyItem())
+ }
+ var currRow = createNewRowLayout(inflater)
+ for (i in 0 until listItemsToShow.size) {
+ val item = listItemsToShow[i]
val itemView = item.toView(context, inflater)
- // If this item will be in the first row (contentView only has description view) and
- // description is empty, remove top padding of this item.
- if (contentView.childCount == 1 && description.isNullOrEmpty()) {
- itemView.setPadding(
- itemView.paddingLeft,
- 0,
- itemView.paddingRight,
- itemView.paddingBottom
- )
- }
- currRowView.addView(itemView)
+ contentView.removeTopPaddingForFirstRow(description, itemView)
- // If this is the first item in the current row, add space behind it.
- if (currRowView.childCount == 1 && showTwoColumn) {
- currRowView.addSpaceView(
- resources.getDimensionPixelSize(
- R.dimen.biometric_prompt_content_space_width_between_items
- ),
- MATCH_PARENT
- )
+ // If there should be two column, and there is already one item in the current row, add
+ // space between two items.
+ if (showTwoColumn && currRow.childCount == 1) {
+ currRow.addSpaceViewBetweenListItem()
}
+ currRow.addView(itemView)
- // If there are already two items (plus the space view) in the current row, or it
- // should be one column, start a new row
- if (currRowView.childCount == 3 || !showTwoColumn) {
- contentView.addView(currRowView)
- currRowView = createNewRowLayout(inflater)
+ // If there should be one column, or there are already two items (plus the space view) in
+ // the current row, or it's already the last item, start a new row
+ if (!showTwoColumn || currRow.childCount == 3 || i == listItemsToShow.size - 1) {
+ contentView.addView(currRow)
+ currRow = createNewRowLayout(inflater)
}
}
- if (currRowView.childCount > 0) {
- contentView.addView(currRowView)
- }
return contentView
}
@@ -170,10 +161,6 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
}
-private fun LinearLayout.addSpaceView(width: Int, height: Int) {
- addView(Space(context), LinearLayout.LayoutParams(width, height))
-}
-
private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
context: Context,
containerViewWidth: Int,
@@ -194,7 +181,10 @@ private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
val contentViewPadding =
resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal)
val listItemPadding = getListItemPadding(resources)
- val maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding
+ var maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding
+ // Reduce maxWidth a bit since paint#measureText is not accurate. See b/330909104 for
+ // more context.
+ maxWidth -= contentViewPadding / 2
val paint = TextPaint()
val attributes =
@@ -224,6 +214,7 @@ private fun PromptContentItem.toView(
inflater: LayoutInflater,
): TextView {
val resources = context.resources
+ // Somehow xml layout params settings doesn't work, set it again here.
val textView =
inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView
val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
@@ -251,6 +242,29 @@ private fun PromptContentItem.toView(
return textView
}
+/* [contentView] function */
+private fun LinearLayout.addSpaceViewBetweenListItem() =
+ addView(
+ Space(context),
+ LinearLayout.LayoutParams(
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_space_width_between_items
+ ),
+ MATCH_PARENT
+ )
+ )
+
+/* [contentView] function*/
+private fun LinearLayout.removeTopPaddingForFirstRow(description: String?, itemView: TextView) {
+ // If this item will be in the first row (contentView only has description view and
+ // description is empty), remove top padding of this item.
+ if (description.isNullOrEmpty() && childCount == 1) {
+ itemView.setPadding(itemView.paddingLeft, 0, itemView.paddingRight, itemView.paddingBottom)
+ }
+}
+
+private fun dummyItem(): PromptContentItem = PromptContentItemPlainText("")
+
private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
var listItemPadding =
resources.getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b2ade4fa1e8a..76d46ed9889f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -288,12 +288,14 @@ object BiometricViewBinder {
// set padding
launch {
viewModel.promptPadding.collect { promptPadding ->
- view.setPadding(
- promptPadding.left,
- promptPadding.top,
- promptPadding.right,
- promptPadding.bottom
- )
+ if (!constraintBp()) {
+ view.setPadding(
+ promptPadding.left,
+ promptPadding.top,
+ promptPadding.right,
+ promptPadding.bottom
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e3c0cba42e2d..f380746105ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -20,7 +20,6 @@ import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.graphics.Outline
-import android.graphics.Rect
import android.transition.AutoTransition
import android.transition.TransitionManager
import android.util.TypedValue
@@ -47,17 +46,14 @@ import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.biometrics.ui.viewmodel.isBottom
import com.android.systemui.biometrics.ui.viewmodel.isLarge
import com.android.systemui.biometrics.ui.viewmodel.isLeft
import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
-import com.android.systemui.biometrics.ui.viewmodel.isRight
import com.android.systemui.biometrics.ui.viewmodel.isSmall
-import com.android.systemui.biometrics.ui.viewmodel.isTop
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import kotlin.math.min
+import kotlin.math.abs
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -97,8 +93,6 @@ object BiometricViewSizeBinder {
if (constraintBp()) {
val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
- val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
- val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
@@ -121,165 +115,12 @@ object BiometricViewSizeBinder {
val largeConstraintSet = ConstraintSet()
largeConstraintSet.clone(mediumConstraintSet)
+ largeConstraintSet.constrainMaxWidth(R.id.panel, view.width)
// TODO: Investigate better way to handle 180 rotations
val flipConstraintSet = ConstraintSet()
- flipConstraintSet.clone(mediumConstraintSet)
- flipConstraintSet.connect(
- R.id.scrollView,
- ConstraintSet.START,
- R.id.midGuideline,
- ConstraintSet.START
- )
- flipConstraintSet.connect(
- R.id.scrollView,
- ConstraintSet.END,
- R.id.rightGuideline,
- ConstraintSet.END
- )
- flipConstraintSet.setHorizontalBias(R.id.biometric_icon, .2f)
-
- // Round the panel outline
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width,
- view.height + cornerRadiusPx,
- cornerRadiusPx.toFloat()
- )
- }
- }
view.doOnLayout {
- val windowBounds = windowManager.maximumWindowMetrics.bounds
- val bottomInset =
- windowManager.maximumWindowMetrics.windowInsets
- .getInsets(WindowInsets.Type.navigationBars())
- .bottom
-
- // TODO: Move to viewmodel
- fun measureBounds(position: PromptPosition) {
- val density = windowManager.currentWindowMetrics.density
- val width = min((640 * density).toInt(), windowBounds.width())
-
- var left = -1
- var right = -1
- var bottom = -1
- var mid = -1
-
- when {
- position.isTop -> {
- // Round bottom corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- -cornerRadiusPx,
- view.width,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
- left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
- }
- position.isBottom -> {
- // Round top corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width,
- view.height + cornerRadiusPx,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = windowBounds.centerX() - width / 2
- right = windowBounds.centerX() - width / 2
- bottom = if (view.isLandscape()) bottomInset else 0
- }
- position.isLeft -> {
- // Round right corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- -cornerRadiusPx,
- 0,
- view.width,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = 0
- mid = (windowBounds.width() * .85).toInt() / 2
- right = windowBounds.width() - (windowBounds.width() * .85).toInt()
- bottom = if (view.isLandscape()) bottomInset else 0
- }
- position.isRight -> {
- // Round left corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width + cornerRadiusPx,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = windowBounds.width() - (windowBounds.width() * .85).toInt()
- right = 0
- bottom = if (view.isLandscape()) bottomInset else 0
- mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2
- }
- }
-
- val bounds = Rect(left, mid, right, bottom)
- if (bounds.shouldAdjustLeftGuideline()) {
- leftGuideline.setGuidelineBegin(bounds.left)
- smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
- mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
- }
- if (bounds.shouldAdjustRightGuideline()) {
- rightGuideline.setGuidelineEnd(bounds.right)
- smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
- mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
- }
- if (bounds.shouldAdjustBottomGuideline()) {
- bottomGuideline.setGuidelineEnd(bounds.bottom)
- smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
- mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
- }
-
- if (position.isBottom) {
- topGuideline.setGuidelinePercent(.25f)
- mediumConstraintSet.setGuidelinePercent(topGuideline.id, .25f)
- } else {
- topGuideline.setGuidelinePercent(0f)
- mediumConstraintSet.setGuidelinePercent(topGuideline.id, 0f)
- }
-
- if (mid != -1 && midGuideline != null) {
- midGuideline.setGuidelineBegin(mid)
- }
- }
-
fun setVisibilities(size: PromptSize) {
viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
@@ -297,36 +138,151 @@ object BiometricViewSizeBinder {
}
}
+ fun roundCorners(size: PromptSize, position: PromptPosition) {
+ var left = 0
+ var top = 0
+ var right = 0
+ var bottom = 0
+ when (size) {
+ PromptSize.SMALL,
+ PromptSize.MEDIUM ->
+ when (position) {
+ PromptPosition.Right -> {
+ left = 0
+ top = 0
+ right = view.width + cornerRadiusPx
+ bottom = view.height
+ }
+ PromptPosition.Left -> {
+ left = -cornerRadiusPx
+ top = 0
+ right = view.width
+ bottom = view.height
+ }
+ PromptPosition.Top -> {
+ left = 0
+ top = -cornerRadiusPx
+ right = panelView.width
+ bottom = view.height
+ }
+ PromptPosition.Bottom -> {
+ left = 0
+ top = 0
+ right = panelView.width
+ bottom = view.height + cornerRadiusPx
+ }
+ }
+ PromptSize.LARGE -> {
+ left = 0
+ top = 0
+ right = view.width
+ bottom = view.height
+ }
+ }
+
+ // Round the panel outline
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ left,
+ top,
+ right,
+ bottom,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
+ }
+
view.repeatWhenAttached {
var currentSize: PromptSize? = null
lifecycleScope.launch {
+ viewModel.guidelineBounds.collect { bounds ->
+ if (bounds.left >= 0) {
+ mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ } else if (bounds.left < 0) {
+ mediumConstraintSet.setGuidelineEnd(
+ leftGuideline.id,
+ abs(bounds.left)
+ )
+ smallConstraintSet.setGuidelineEnd(
+ leftGuideline.id,
+ abs(bounds.left)
+ )
+ }
+
+ if (bounds.right >= 0) {
+ mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ } else if (bounds.right < 0) {
+ mediumConstraintSet.setGuidelineBegin(
+ rightGuideline.id,
+ abs(bounds.right)
+ )
+ smallConstraintSet.setGuidelineBegin(
+ rightGuideline.id,
+ abs(bounds.right)
+ )
+ }
+
+ if (midGuideline != null) {
+ if (bounds.bottom >= 0) {
+ midGuideline.setGuidelineEnd(bounds.bottom)
+ mediumConstraintSet.setGuidelineEnd(
+ midGuideline.id,
+ bounds.bottom
+ )
+ } else if (bounds.bottom < 0) {
+ midGuideline.setGuidelineBegin(abs(bounds.bottom))
+ mediumConstraintSet.setGuidelineBegin(
+ midGuideline.id,
+ abs(bounds.bottom)
+ )
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
combine(viewModel.position, viewModel.size, ::Pair).collect {
(position, size) ->
view.doOnAttach {
+ setVisibilities(size)
+
if (position.isLeft) {
- flipConstraintSet.applyTo(view)
- } else if (position.isRight) {
- mediumConstraintSet.applyTo(view)
+ if (size.isSmall) {
+ flipConstraintSet.clone(smallConstraintSet)
+ } else {
+ flipConstraintSet.clone(mediumConstraintSet)
+ }
+
+ // Move all content to other panel
+ flipConstraintSet.connect(
+ R.id.scrollView,
+ ConstraintSet.START,
+ R.id.midGuideline,
+ ConstraintSet.START
+ )
+ flipConstraintSet.connect(
+ R.id.scrollView,
+ ConstraintSet.END,
+ R.id.rightGuideline,
+ ConstraintSet.END
+ )
}
- measureBounds(position)
- setVisibilities(size)
+ roundCorners(size, position)
+
when {
size.isSmall -> {
- val ratio =
- if (view.isLandscape()) {
- (windowBounds.height() -
- bottomInset -
- viewModel.promptMargin)
- .toFloat() / windowBounds.height()
- } else {
- (windowBounds.height() - viewModel.promptMargin)
- .toFloat() / windowBounds.height()
- }
- smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
-
- smallConstraintSet.applyTo(view as ConstraintLayout?)
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ smallConstraintSet.applyTo(view)
+ }
}
size.isMedium && currentSize.isSmall -> {
val autoTransition = AutoTransition()
@@ -338,7 +294,19 @@ object BiometricViewSizeBinder {
view,
autoTransition
)
- mediumConstraintSet.applyTo(view)
+
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ mediumConstraintSet.applyTo(view)
+ }
+ }
+ size.isMedium -> {
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ mediumConstraintSet.applyTo(view)
+ }
}
size.isLarge -> {
val autoTransition = AutoTransition()
@@ -551,20 +519,6 @@ private fun View.showContentOrHide(forceHide: Boolean = false) {
}
}
-private fun View.centerX(): Int {
- return (x + width / 2).toInt()
-}
-
-private fun View.centerY(): Int {
- return (y + height / 2).toInt()
-}
-
-private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
-
-private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
-
-private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
-
private fun View.asVerticalAnimator(
duration: Long,
toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index d8265c7d40e8..66b7d7afb8c4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -37,7 +37,6 @@ import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/** Sub-binder for [BiometricPromptLayout.iconView]. */
@@ -127,14 +126,22 @@ object PromptIconViewBinder {
if (constraintBp() && position != Rect()) {
val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
- if (position.left != -1) {
+ if (position.left != 0) {
iconParams.endToEnd = ConstraintSet.UNSET
iconParams.leftMargin = position.left
}
- if (position.top != -1) {
+ if (position.top != 0) {
iconParams.bottomToBottom = ConstraintSet.UNSET
iconParams.topMargin = position.top
}
+ if (position.right != 0) {
+ iconParams.startToStart = ConstraintSet.UNSET
+ iconParams.rightMargin = position.right
+ }
+ if (position.bottom != 0) {
+ iconParams.topToTop = ConstraintSet.UNSET
+ iconParams.bottomMargin = position.bottom
+ }
iconView.layoutParams = iconParams
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index d0c140b353e2..8dbed5f2e323 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -94,25 +94,76 @@ constructor(
params.naturalDisplayHeight,
rotation.ordinal
)
- rotatedBounds
+ Rect(
+ rotatedBounds.left,
+ rotatedBounds.top,
+ params.logicalDisplayWidth - rotatedBounds.right,
+ params.logicalDisplayHeight - rotatedBounds.bottom
+ )
}
.distinctUntilChanged()
val iconPosition: Flow<Rect> =
- combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
- sensorBounds,
- size,
- modalities ->
- // If not Udfps, icon does not change from default layout position
- if (!modalities.hasUdfps) {
- Rect() // Empty rect, don't offset from default position
- } else if (size.isSmall) {
- // When small with Udfps, only set horizontal position
- Rect(sensorBounds.left, -1, sensorBounds.right, -1)
- } else {
- sensorBounds
+ combine(
+ udfpsSensorBounds,
+ promptViewModel.size,
+ promptViewModel.position,
+ promptViewModel.modalities
+ ) { sensorBounds, size, position, modalities ->
+ when (position) {
+ PromptPosition.Bottom ->
+ if (size.isSmall) {
+ Rect(0, 0, 0, promptViewModel.portraitSmallBottomPadding)
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(0, 0, 0, sensorBounds.bottom)
+ } else if (size.isMedium) {
+ Rect(0, 0, 0, promptViewModel.portraitMediumBottomPadding)
+ } else {
+ // Large screen
+ Rect(0, 0, 0, promptViewModel.portraitLargeScreenBottomPadding)
+ }
+ PromptPosition.Right ->
+ if (size.isSmall || modalities.hasFaceOnly) {
+ Rect(
+ 0,
+ 0,
+ promptViewModel.landscapeSmallHorizontalPadding,
+ promptViewModel.landscapeSmallBottomPadding
+ )
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(0, 0, sensorBounds.right, sensorBounds.bottom)
+ } else {
+ // SFPS
+ Rect(
+ 0,
+ 0,
+ promptViewModel.landscapeMediumHorizontalPadding,
+ promptViewModel.landscapeMediumBottomPadding
+ )
+ }
+ PromptPosition.Left ->
+ if (size.isSmall || modalities.hasFaceOnly) {
+ Rect(
+ promptViewModel.landscapeSmallHorizontalPadding,
+ 0,
+ 0,
+ promptViewModel.landscapeSmallBottomPadding
+ )
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(sensorBounds.left, 0, 0, sensorBounds.bottom)
+ } else {
+ // SFPS
+ Rect(
+ promptViewModel.landscapeMediumHorizontalPadding,
+ 0,
+ 0,
+ promptViewModel.landscapeMediumBottomPadding
+ )
+ }
+ PromptPosition.Top -> Rect()
+ }
}
- }
+ .distinctUntilChanged()
/** Whether an error message is currently being shown. */
val showingError = promptViewModel.showingError
@@ -162,10 +213,11 @@ constructor(
val iconSize: Flow<Pair<Int, Int>> =
combine(
+ promptViewModel.position,
activeAuthType,
promptViewModel.fingerprintSensorWidth,
promptViewModel.fingerprintSensorHeight,
- ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
+ ) { _, activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
if (activeAuthType == AuthType.Face) {
Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index fbd87fd3a9f5..21ebff4d0b71 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -86,6 +86,36 @@ constructor(
val faceIconHeight: Int =
context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ /** Padding for placing icons */
+ val portraitSmallBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_small_bottom_padding
+ )
+ val portraitMediumBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_medium_bottom_padding
+ )
+ val portraitLargeScreenBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_large_screen_bottom_padding
+ )
+ val landscapeSmallBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_small_bottom_padding
+ )
+ val landscapeSmallHorizontalPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_small_horizontal_padding
+ )
+ val landscapeMediumBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_medium_bottom_padding
+ )
+ val landscapeMediumHorizontalPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_medium_horizontal_padding
+ )
+
val fingerprintSensorWidth: Flow<Int> =
combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
->
@@ -111,9 +141,6 @@ constructor(
/** Hint for talkback directional guidance */
val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
- val promptMargin: Int =
- context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
-
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -205,6 +232,66 @@ constructor(
}
.distinctUntilChanged()
+ /** Prompt panel size padding */
+ private val smallHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_small_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_udfps_horizontal_guideline_padding
+ )
+ private val udfpsMidGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_udfps_mid_guideline_padding
+ )
+ private val mediumHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_medium_horizontal_guideline_padding
+ )
+ private val mediumMidGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_medium_mid_guideline_padding
+ )
+
+ /**
+ * Rect for positioning prompt guidelines (left, top, right, mid)
+ *
+ * Negative values are used to signify that guideline measuring should be flipped, measuring
+ * from opposite side of the screen
+ */
+ val guidelineBounds: Flow<Rect> =
+ combine(size, position, modalities) { size, position, modalities ->
+ if (position.isBottom) {
+ Rect(0, 0, 0, 0)
+ } else if (position.isRight) {
+ if (size.isSmall) {
+ Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
+ } else if (modalities.hasUdfps) {
+ Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding)
+ } else if (modalities.isEmpty) {
+ // TODO: Temporary fix until no biometric landscape layout is added
+ Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6)
+ } else {
+ Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding)
+ }
+ } else if (position.isLeft) {
+ if (size.isSmall) {
+ Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
+ } else if (modalities.hasUdfps) {
+ Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding)
+ } else if (modalities.isEmpty) {
+ // TODO: Temporary fix until no biometric landscape layout is added
+ Rect(0, 0, -mediumHorizontalGuidelinePadding, -6)
+ } else {
+ Rect(0, 0, -mediumHorizontalGuidelinePadding, -mediumMidGuidelinePadding)
+ }
+ } else {
+ Rect()
+ }
+ }
+ .distinctUntilChanged()
+
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication. Confirmation always required when in explicit flow.
@@ -424,7 +511,7 @@ constructor(
isAuthenticated,
promptSelectorInteractor.isCredentialAllowed,
) { size, _, authState, credentialAllowed ->
- size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
+ size.isMedium && authState.isNotAuthenticated && credentialAllowed
}
private val history = PromptHistoryImpl()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index cfda75c7851a..b72b1f35595a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -28,6 +28,7 @@ import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -155,17 +156,19 @@ constructor(
->
val topLeft = Point(sensorLocation.left, sensorLocation.top)
- if (sensorLocation.isSensorVerticalInDefaultOrientation) {
- if (displayRotation == DisplayRotation.ROTATION_0) {
- topLeft.x -= bounds!!.width()
- } else if (displayRotation == DisplayRotation.ROTATION_270) {
- topLeft.y -= bounds!!.height()
- }
- } else {
- if (displayRotation == DisplayRotation.ROTATION_180) {
- topLeft.y -= bounds!!.height()
- } else if (displayRotation == DisplayRotation.ROTATION_270) {
- topLeft.x -= bounds!!.width()
+ if (!constraintBp()) {
+ if (sensorLocation.isSensorVerticalInDefaultOrientation) {
+ if (displayRotation == DisplayRotation.ROTATION_0) {
+ topLeft.x -= bounds!!.width()
+ } else if (displayRotation == DisplayRotation.ROTATION_270) {
+ topLeft.y -= bounds!!.height()
+ }
+ } else {
+ if (displayRotation == DisplayRotation.ROTATION_180) {
+ topLeft.y -= bounds!!.height()
+ } else if (displayRotation == DisplayRotation.ROTATION_270) {
+ topLeft.x -= bounds!!.width()
+ }
}
}
defaultOverlayViewParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
index 59fc81c82df0..f86cad5b9c59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
index 9ee582a77862..81fe2a5a5f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 9c63a30dfc1c..94d7af74f1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAdapter.STATE_OFF
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a8d9e781228b..c7d171d5b804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.os.Bundle
import android.view.LayoutInflater
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 5d18dc1d453a..c30aea07e959 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
index ea51beecc2c1..6e51915797cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index cd52e0dcca4a..add1647143d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index fd624d2f1ba1..e65b65710f94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.content.Intent
import android.content.SharedPreferences
@@ -31,15 +31,15 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 1c621b87533d..dc5efefdfb16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -30,7 +30,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.graphics.drawable.Drawable
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 56ba07941e4d..f04ba75ca3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index fce25ec68190..4e28cafb5004 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 893887fad176..d88b3dcac598 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -158,6 +159,10 @@ constructor(
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
+ // When the scene container framework is enabled, instead of calling this, call
+ // SceneInteractor#changeScene(Scenes.Bouncer, ...).
+ SceneContainerFlag.assertInLegacyMode()
+
if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) {
Log.d(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index f1a0e5e3539c..78811a96a026 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -12,14 +12,15 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
data class LegacyBouncerDependencies
@Inject
@@ -59,7 +60,7 @@ constructor(
private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
) {
fun bind(view: ViewGroup) {
- if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
+ if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
val deps = composeBouncerDependencies.get()
ComposeBouncerViewBinder.bind(
view,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 62da5c0e5675..12cac9251b25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -99,7 +99,7 @@ class PinBouncerViewModel(
.map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
.stateIn(
scope = viewModelScope,
- started = SharingStarted.Eagerly,
+ started = SharingStarted.WhileSubscribed(),
initialValue = ActionButtonAppearance.Hidden,
)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
new file mode 100644
index 000000000000..2b9fc73458d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.dagger
+
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl
+import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository
+import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface ScreenBrightnessModule {
+
+ @Binds
+ fun bindScreenBrightnessRepository(
+ impl: ScreenBrightnessDisplayManagerRepository
+ ): ScreenBrightnessRepository
+
+ @Binds
+ fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
new file mode 100644
index 000000000000..608f301da85d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.model
+
+@JvmInline
+value class LinearBrightness(val floatValue: Float) {
+ fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
+ return if (floatValue < min.floatValue) {
+ min
+ } else if (floatValue > max.floatValue) {
+ max
+ } else {
+ this
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
new file mode 100644
index 000000000000..c018ecb25835
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import android.content.Context
+import android.os.UserManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.PolicyRestriction
+import com.android.systemui.utils.UserRestrictionChecker
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+
+/** Checks whether the current user is restricted to change the brightness ([RESTRICTION]) */
+interface BrightnessPolicyRepository {
+
+ /**
+ * Indicates whether the current user is restricted to change the brightness. As there is no way
+ * to determine when a restriction has been added/removed. This value may be fetched eagerly and
+ * not updated (unless the user changes) per flow.
+ */
+ val restrictionPolicy: Flow<PolicyRestriction>
+
+ companion object {
+ const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class BrightnessPolicyRepositoryImpl
+@Inject
+constructor(
+ userRepository: UserRepository,
+ private val userRestrictionChecker: UserRestrictionChecker,
+ @Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : BrightnessPolicyRepository {
+ override val restrictionPolicy =
+ userRepository.selectedUserInfo
+ .mapLatest { user ->
+ userRestrictionChecker
+ .checkIfRestrictionEnforced(
+ applicationContext,
+ BrightnessPolicyRepository.RESTRICTION,
+ user.id
+ )
+ ?.let { PolicyRestriction.Restricted(it) }
+ ?: PolicyRestriction.NoRestriction
+ }
+ .flowOn(backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
new file mode 100644
index 000000000000..9ed11d13d4d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.data.repository
+
+import android.annotation.SuppressLint
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.DisplayManager
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.DisplayId
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for tracking brightness in the current display.
+ *
+ * Values are in a linear space, as used by [DisplayManager].
+ */
+interface ScreenBrightnessRepository {
+ /** Current brightness as a value between [minLinearBrightness] and [maxLinearBrightness] */
+ val linearBrightness: Flow<LinearBrightness>
+
+ /** Current minimum value for the brightness */
+ val minLinearBrightness: Flow<LinearBrightness>
+
+ /** Current maximum value for the brightness */
+ val maxLinearBrightness: Flow<LinearBrightness>
+
+ /** Gets the current values for min and max brightness */
+ suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness>
+
+ /**
+ * Sets the temporary value for the brightness. This should change the display brightness but
+ * not trigger any updates.
+ */
+ fun setTemporaryBrightness(value: LinearBrightness)
+
+ /** Sets the brightness definitively. */
+ fun setBrightness(value: LinearBrightness)
+}
+
+@SuppressLint("MissingPermission")
+@SysUISingleton
+class ScreenBrightnessDisplayManagerRepository
+@Inject
+constructor(
+ @DisplayId private val displayId: Int,
+ private val displayManager: DisplayManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundContext: CoroutineContext,
+) : ScreenBrightnessRepository {
+
+ private val apiQueue =
+ Channel<SetBrightnessMethod>(
+ capacity = UNLIMITED,
+ )
+
+ init {
+ applicationScope.launch(backgroundContext) {
+ for (call in apiQueue) {
+ val bounds = getMinMaxLinearBrightness()
+ val value = call.value.clamp(bounds.first, bounds.second).floatValue
+ when (call) {
+ is SetBrightnessMethod.Temporary -> {
+ displayManager.setTemporaryBrightness(displayId, value)
+ }
+ is SetBrightnessMethod.Permanent -> {
+ displayManager.setBrightness(displayId, value)
+ }
+ }
+ }
+ }
+ }
+
+ private val brightnessInfo: StateFlow<BrightnessInfo?> =
+ conflatedCallbackFlow {
+ val listener =
+ object : DisplayManager.DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {}
+
+ override fun onDisplayRemoved(displayId: Int) {}
+
+ override fun onDisplayChanged(displayId: Int) {
+ if (
+ displayId == this@ScreenBrightnessDisplayManagerRepository.displayId
+ ) {
+ trySend(Unit)
+ }
+ }
+ }
+ displayManager.registerDisplayListener(
+ listener,
+ null,
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS,
+ )
+
+ awaitClose { displayManager.unregisterDisplayListener(listener) }
+ }
+ .onStart { emit(Unit) }
+ .map { brightnessInfoValue() }
+ .flowOn(backgroundContext)
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0L),
+ null,
+ )
+
+ private suspend fun brightnessInfoValue(): BrightnessInfo? {
+ return withContext(backgroundContext) {
+ displayManager.getDisplay(displayId).brightnessInfo
+ }
+ }
+
+ override val minLinearBrightness =
+ brightnessInfo
+ .filterNotNull()
+ .map { LinearBrightness(it.brightnessMinimum) }
+ .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+ override val maxLinearBrightness =
+ brightnessInfo
+ .filterNotNull()
+ .map { LinearBrightness(it.brightnessMaximum) }
+ .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+ override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+ val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue()
+ val min = brightnessInfo?.brightnessMinimum ?: 0f
+ val max = brightnessInfo?.brightnessMaximum ?: 1f
+ return LinearBrightness(min) to LinearBrightness(max)
+ }
+
+ override val linearBrightness =
+ brightnessInfo
+ .filterNotNull()
+ .map { LinearBrightness(it.brightness) }
+ .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+ override fun setTemporaryBrightness(value: LinearBrightness) {
+ apiQueue.trySend(SetBrightnessMethod.Temporary(value))
+ }
+
+ override fun setBrightness(value: LinearBrightness) {
+ apiQueue.trySend(SetBrightnessMethod.Permanent(value))
+ }
+
+ private sealed interface SetBrightnessMethod {
+ val value: LinearBrightness
+ @JvmInline
+ value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod
+ @JvmInline
+ value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.kt
new file mode 100644
index 000000000000..fb00edf031d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.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.brightness.domain.interactor
+
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.utils.PolicyRestriction
+import javax.inject.Inject
+
+/** Interactor for enforcing the policy that may disallow brightness changing. */
+@SysUISingleton
+class BrightnessPolicyEnforcementInteractor
+@Inject
+constructor(
+ brightnessPolicyRepository: BrightnessPolicyRepository,
+ private val activityStarter: ActivityStarter,
+) {
+
+ /** Brightness policy restriction for the current user. */
+ val brightnessPolicyRestriction = brightnessPolicyRepository.restrictionPolicy
+
+ /**
+ * Starts the dialog with details about the current restriction for changing brightness. Should
+ * be triggered when a restricted user tries to change the brightness.
+ */
+ fun startAdminSupportDetailsDialog(restriction: PolicyRestriction.Restricted) {
+ val intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(restriction.admin)
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
new file mode 100644
index 000000000000..799a0a14c99d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.domain.interactor
+
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Converts between [GammaBrightness] and [LinearBrightness].
+ *
+ * @see BrightnessUtils
+ */
+@SysUISingleton
+class ScreenBrightnessInteractor
+@Inject
+constructor(
+ private val screenBrightnessRepository: ScreenBrightnessRepository,
+) {
+ /** Maximum value in the Gamma space for brightness */
+ val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX)
+
+ /** Minimum value in the Gamma space for brightness */
+ val minGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN)
+
+ /**
+ * Brightness in the Gamma space for the current display. It will always represent a value
+ * between [minGammaBrightness] and [maxGammaBrightness]
+ */
+ val gammaBrightness =
+ with(screenBrightnessRepository) {
+ combine(
+ linearBrightness,
+ minLinearBrightness,
+ maxLinearBrightness,
+ ) { brightness, min, max ->
+ brightness.toGammaBrightness(min, max)
+ }
+ }
+
+ /** Sets the brightness temporarily, while the user is changing it. */
+ suspend fun setTemporaryBrightness(gammaBrightness: GammaBrightness) {
+ screenBrightnessRepository.setTemporaryBrightness(
+ gammaBrightness.clamp().toLinearBrightness()
+ )
+ }
+
+ /** Sets the brightness definitely. */
+ suspend fun setBrightness(gammaBrightness: GammaBrightness) {
+ screenBrightnessRepository.setBrightness(gammaBrightness.clamp().toLinearBrightness())
+ }
+
+ private suspend fun GammaBrightness.toLinearBrightness(): LinearBrightness {
+ val bounds = screenBrightnessRepository.getMinMaxLinearBrightness()
+ return LinearBrightness(
+ BrightnessUtils.convertGammaToLinearFloat(
+ value,
+ bounds.first.floatValue,
+ bounds.second.floatValue
+ )
+ )
+ }
+
+ private fun GammaBrightness.clamp(): GammaBrightness {
+ return GammaBrightness(value.coerceIn(minGammaBrightness.value, maxGammaBrightness.value))
+ }
+
+ private fun LinearBrightness.toGammaBrightness(
+ min: LinearBrightness,
+ max: LinearBrightness,
+ ): GammaBrightness {
+ return GammaBrightness(
+ BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue)
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
new file mode 100644
index 000000000000..e20d003bb989
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.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.brightness.shared
+
+import androidx.annotation.IntRange
+import com.android.settingslib.display.BrightnessUtils
+
+@JvmInline
+value class GammaBrightness(
+ @IntRange(
+ from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
+ to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
+ )
+ val value: Int
+)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
new file mode 100644
index 000000000000..c1be37af3aeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.ui.compose
+
+import androidx.compose.animation.core.animateIntAsState
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformSlider
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.brightness.ui.viewmodel.Drag
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.utils.PolicyRestriction
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@Composable
+private fun BrightnessSlider(
+ gammaValue: Int,
+ valueRange: IntRange,
+ label: Text.Resource,
+ icon: Icon,
+ restriction: PolicyRestriction,
+ onRestrictedClick: (PolicyRestriction.Restricted) -> Unit,
+ onDrag: (Int) -> Unit,
+ onStop: (Int) -> Unit,
+ modifier: Modifier = Modifier,
+ formatter: (Int) -> String = { "$it" },
+) {
+ var value by remember(gammaValue) { mutableIntStateOf(gammaValue) }
+ val animatedValue by
+ animateIntAsState(targetValue = value, label = "BrightnessSliderAnimatedValue")
+ val floatValueRange = valueRange.first.toFloat()..valueRange.last.toFloat()
+ val isRestricted = restriction is PolicyRestriction.Restricted
+
+ PlatformSlider(
+ value = animatedValue.toFloat(),
+ valueRange = floatValueRange,
+ enabled = !isRestricted,
+ onValueChange = {
+ if (!isRestricted) {
+ value = it.toInt()
+ onDrag(value)
+ }
+ },
+ onValueChangeFinished = {
+ if (!isRestricted) {
+ onStop(value)
+ }
+ },
+ modifier =
+ modifier.clickable(
+ enabled = isRestricted,
+ ) {
+ if (restriction is PolicyRestriction.Restricted) {
+ onRestrictedClick(restriction)
+ }
+ },
+ icon = { isDragging ->
+ if (isDragging) {
+ Text(text = formatter(value))
+ } else {
+ Icon(modifier = Modifier.size(24.dp), icon = icon)
+ }
+ },
+ label = {
+ Text(
+ text = stringResource(id = label.res),
+ style = MaterialTheme.typography.titleMedium,
+ maxLines = 1,
+ )
+ },
+ )
+}
+
+@Composable
+fun BrightnessSliderContainer(
+ viewModel: BrightnessSliderViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0)
+ val coroutineScope = rememberCoroutineScope()
+ val restriction by
+ viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction)
+
+ BrightnessSlider(
+ gammaValue = gamma,
+ valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
+ label = viewModel.label,
+ icon = viewModel.icon,
+ restriction = restriction,
+ onRestrictedClick = viewModel::showPolicyRestrictionDialog,
+ onDrag = { coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) } },
+ onStop = { coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) } },
+ modifier = modifier.fillMaxWidth(),
+ formatter = viewModel::formatValue,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
new file mode 100644
index 000000000000..f0988ba96bcd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.ui.viewmodel
+
+import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
+import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.utils.PolicyRestriction
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessSliderViewModel
+@Inject
+constructor(
+ private val screenBrightnessInteractor: ScreenBrightnessInteractor,
+ private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
+) {
+ val currentBrightness = screenBrightnessInteractor.gammaBrightness
+
+ val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
+ val minBrightness = screenBrightnessInteractor.minGammaBrightness
+
+ val label = Text.Resource(R.string.quick_settings_brightness_dialog_title)
+
+ val icon = Icon.Resource(R.drawable.ic_brightness_full, ContentDescription.Resource(label.res))
+
+ val policyRestriction = brightnessPolicyEnforcementInteractor.brightnessPolicyRestriction
+
+ fun showPolicyRestrictionDialog(restriction: PolicyRestriction.Restricted) {
+ brightnessPolicyEnforcementInteractor.startAdminSupportDetailsDialog(restriction)
+ }
+
+ /**
+ * As a brightness slider is dragged, the corresponding events should be sent using this method.
+ */
+ suspend fun onDrag(drag: Drag) {
+ when (drag) {
+ is Drag.Dragging -> screenBrightnessInteractor.setTemporaryBrightness(drag.brightness)
+ is Drag.Stopped -> screenBrightnessInteractor.setBrightness(drag.brightness)
+ }
+ }
+
+ /**
+ * Format the current value of brightness as a percentage between the minimum and maximum gamma.
+ */
+ fun formatValue(value: Int): String {
+ val min = minBrightness.value
+ val max = maxBrightness.value
+ val coercedValue = value.coerceIn(min, max)
+ val percentage = (coercedValue - min) * 100 / (max - min)
+ // This is not finalized UI so using fixed string
+ return "$percentage%"
+ }
+}
+
+/** Represents a drag event in a brightness slider. */
+sealed interface Drag {
+ val brightness: GammaBrightness
+ @JvmInline value class Dragging(override val brightness: GammaBrightness) : Drag
+ @JvmInline value class Stopped(override val brightness: GammaBrightness) : Drag
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 357eca37ba37..d2caefd3b552 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -398,6 +398,7 @@ public class BrightLineFalsingManager implements FalsingManager {
|| mAccessibilityManager.isTouchExplorationEnabled()
|| mDataProvider.isA11yAction()
|| mDataProvider.isFromTrackpad()
+ || mDataProvider.isFromKeyboard()
|| (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
&& mDataProvider.isUnfolded());
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index a79a654aedc2..76b228d4726a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
/**
@@ -50,6 +51,14 @@ public interface FalsingCollector {
void onBouncerHidden();
/**
+ * Call this to record a KeyEvent in the {@link com.android.systemui.plugins.FalsingManager}.
+ *
+ * This may decide to only collect certain KeyEvents and ignore others. Do not assume all
+ * KeyEvents are collected.
+ */
+ void onKeyEvent(KeyEvent ev);
+
+ /**
* Call this to record a MotionEvent in the {@link com.android.systemui.plugins.FalsingManager}.
*
* Be sure to call {@link #onMotionEventComplete()} after the rest of SystemUI is done with the
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index d6b9a119e31c..dcd419512498 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import javax.inject.Inject;
@@ -23,6 +24,8 @@ import javax.inject.Inject;
/** */
public class FalsingCollectorFake implements FalsingCollector {
+ public KeyEvent lastKeyEvent = null;
+
@Override
public void init() {
}
@@ -70,6 +73,11 @@ public class FalsingCollectorFake implements FalsingCollector {
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ lastKeyEvent = ev;
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4f4f3d0324b3..beaa170943fd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dock.DockManager.DockEventListener;
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.VisibleForTesting;
@@ -49,7 +50,10 @@ import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import javax.inject.Inject;
@@ -61,6 +65,14 @@ class FalsingCollectorImpl implements FalsingCollector {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long GESTURE_PROCESSING_DELAY_MS = 100;
+ private final Set<Integer> mAcceptedKeycodes = new HashSet<>(Arrays.asList(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.KEYCODE_SPACE
+ ));
+
private final FalsingDataProvider mFalsingDataProvider;
private final FalsingManager mFalsingManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -279,6 +291,14 @@ class FalsingCollectorImpl implements FalsingCollector {
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ // Only collect if it is an ACTION_UP action and is allow-listed
+ if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) {
+ mFalsingDataProvider.onKeyEvent(ev);
+ }
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index c5d8c795853d..b289fa49d06c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -16,6 +16,7 @@
package com.android.systemui.classifier
+import android.view.KeyEvent
import android.view.MotionEvent
import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
import com.android.systemui.dagger.SysUISingleton
@@ -59,6 +60,10 @@ class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
logDebug("NOOP: onBouncerHidden")
}
+ override fun onKeyEvent(ev: KeyEvent) {
+ logDebug("NOOP: onKeyEvent(${ev.action}")
+ }
+
override fun onTouchEvent(ev: MotionEvent) {
logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 809d5b289cab..15017011134b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,6 +20,7 @@ import static com.android.systemui.classifier.FalsingModule.IS_FOLDABLE_DEVICE;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
@@ -41,6 +42,7 @@ import javax.inject.Named;
public class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
+ private static final long KEY_EVENT_AGE_MS = 500;
private static final long DROP_EVENT_THRESHOLD_MS = 50;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
@@ -56,8 +58,10 @@ public class FalsingDataProvider {
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
- private TimeLimitedMotionEventBuffer mRecentMotionEvents =
- new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ private TimeLimitedInputEventBuffer<MotionEvent> mRecentMotionEvents =
+ new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
+ private final TimeLimitedInputEventBuffer<KeyEvent> mRecentKeyEvents =
+ new TimeLimitedInputEventBuffer<>(KEY_EVENT_AGE_MS);
private List<MotionEvent> mPriorMotionEvents = new ArrayList<>();
private boolean mDirty = true;
@@ -89,6 +93,10 @@ public class FalsingDataProvider {
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
}
+ void onKeyEvent(KeyEvent keyEvent) {
+ mRecentKeyEvents.add(keyEvent);
+ }
+
void onMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size());
@@ -109,6 +117,10 @@ public class FalsingDataProvider {
// previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
mDropLastEvent = shouldDropEvent(motionEvent);
+ if (!motionEvents.isEmpty() && !mRecentKeyEvents.isEmpty()) {
+ recycleAndClearRecentKeyEvents();
+ }
+
mRecentMotionEvents.addAll(motionEvents);
FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -141,7 +153,7 @@ public class FalsingDataProvider {
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
- mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ mRecentMotionEvents = new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
}
mDropLastEvent = false;
mA11YAction = false;
@@ -261,6 +273,13 @@ public class FalsingDataProvider {
return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
}
+ /**
+ * If it's a specific set of keys as collected from {@link FalsingCollector}
+ */
+ public boolean isFromKeyboard() {
+ return !mRecentKeyEvents.isEmpty();
+ }
+
public boolean isFromTrackpad() {
if (mRecentMotionEvents.isEmpty()) {
return false;
@@ -318,6 +337,14 @@ public class FalsingDataProvider {
}
}
+ private void recycleAndClearRecentKeyEvents() {
+ for (KeyEvent ev : mRecentKeyEvents) {
+ ev.recycle();
+ }
+
+ mRecentKeyEvents.clear();
+ }
+
private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = new ArrayList<>();
List<PointerProperties> pointerPropertiesList = new ArrayList<>();
@@ -416,6 +443,8 @@ public class FalsingDataProvider {
mRecentMotionEvents.clear();
+ recycleAndClearRecentKeyEvents();
+
mDirty = true;
mSessionListeners.forEach(SessionListener::onSessionEnded);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
index 51aede79b581..7627ad19f650 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
@@ -16,7 +16,7 @@
package com.android.systemui.classifier;
-import android.view.MotionEvent;
+import android.view.InputEvent;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,31 +25,31 @@ import java.util.List;
import java.util.ListIterator;
/**
- * Maintains an ordered list of the last N milliseconds of MotionEvents.
+ * Maintains an ordered list of the last N milliseconds of InputEvents.
*
* This class is simply a convenience class designed to look like a simple list, but that
- * automatically discards old MotionEvents. It functions much like a queue - first in first out -
+ * automatically discards old InputEvents. It functions much like a queue - first in first out -
* but does not have a fixed size like a circular buffer.
*/
-public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
+public class TimeLimitedInputEventBuffer<T extends InputEvent> implements List<T> {
- private final List<MotionEvent> mMotionEvents;
+ private final List<T> mInputEvents;
private final long mMaxAgeMs;
- public TimeLimitedMotionEventBuffer(long maxAgeMs) {
+ public TimeLimitedInputEventBuffer(long maxAgeMs) {
super();
mMaxAgeMs = maxAgeMs;
- mMotionEvents = new ArrayList<>();
+ mInputEvents = new ArrayList<>();
}
private void ejectOldEvents() {
- if (mMotionEvents.isEmpty()) {
+ if (mInputEvents.isEmpty()) {
return;
}
- Iterator<MotionEvent> iter = listIterator();
- long mostRecentMs = mMotionEvents.get(mMotionEvents.size() - 1).getEventTime();
+ Iterator<T> iter = listIterator();
+ long mostRecentMs = mInputEvents.get(mInputEvents.size() - 1).getEventTime();
while (iter.hasNext()) {
- MotionEvent ev = iter.next();
+ T ev = iter.next();
if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
iter.remove();
ev.recycle();
@@ -58,140 +58,140 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public void add(int index, MotionEvent element) {
+ public void add(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
- public MotionEvent remove(int index) {
- return mMotionEvents.remove(index);
+ public T remove(int index) {
+ return mInputEvents.remove(index);
}
@Override
public int indexOf(Object o) {
- return mMotionEvents.indexOf(o);
+ return mInputEvents.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
- return mMotionEvents.lastIndexOf(o);
+ return mInputEvents.lastIndexOf(o);
}
@Override
public int size() {
- return mMotionEvents.size();
+ return mInputEvents.size();
}
@Override
public boolean isEmpty() {
- return mMotionEvents.isEmpty();
+ return mInputEvents.isEmpty();
}
@Override
public boolean contains(Object o) {
- return mMotionEvents.contains(o);
+ return mInputEvents.contains(o);
}
@Override
- public Iterator<MotionEvent> iterator() {
- return mMotionEvents.iterator();
+ public Iterator<T> iterator() {
+ return mInputEvents.iterator();
}
@Override
public Object[] toArray() {
- return mMotionEvents.toArray();
+ return mInputEvents.toArray();
}
@Override
- public <T> T[] toArray(T[] a) {
- return mMotionEvents.toArray(a);
+ public <T2> T2[] toArray(T2[] a) {
+ return mInputEvents.toArray(a);
}
@Override
- public boolean add(MotionEvent element) {
- boolean result = mMotionEvents.add(element);
+ public boolean add(T element) {
+ boolean result = mInputEvents.add(element);
ejectOldEvents();
return result;
}
@Override
public boolean remove(Object o) {
- return mMotionEvents.remove(o);
+ return mInputEvents.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
- return mMotionEvents.containsAll(c);
+ return mInputEvents.containsAll(c);
}
@Override
- public boolean addAll(Collection<? extends MotionEvent> collection) {
- boolean result = mMotionEvents.addAll(collection);
+ public boolean addAll(Collection<? extends T> collection) {
+ boolean result = mInputEvents.addAll(collection);
ejectOldEvents();
return result;
}
@Override
- public boolean addAll(int index, Collection<? extends MotionEvent> elements) {
+ public boolean addAll(int index, Collection<? extends T> elements) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
- return mMotionEvents.removeAll(c);
+ return mInputEvents.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
- return mMotionEvents.retainAll(c);
+ return mInputEvents.retainAll(c);
}
@Override
public void clear() {
- mMotionEvents.clear();
+ mInputEvents.clear();
}
@Override
public boolean equals(Object o) {
- return mMotionEvents.equals(o);
+ return mInputEvents.equals(o);
}
@Override
public int hashCode() {
- return mMotionEvents.hashCode();
+ return mInputEvents.hashCode();
}
@Override
- public MotionEvent get(int index) {
- return mMotionEvents.get(index);
+ public T get(int index) {
+ return mInputEvents.get(index);
}
@Override
- public MotionEvent set(int index, MotionEvent element) {
+ public T set(int index, T element) {
throw new UnsupportedOperationException();
}
@Override
- public ListIterator<MotionEvent> listIterator() {
+ public ListIterator<T> listIterator() {
return new Iter(0);
}
@Override
- public ListIterator<MotionEvent> listIterator(int index) {
+ public ListIterator<T> listIterator(int index) {
return new Iter(index);
}
@Override
- public List<MotionEvent> subList(int fromIndex, int toIndex) {
- return mMotionEvents.subList(fromIndex, toIndex);
+ public List<T> subList(int fromIndex, int toIndex) {
+ return mInputEvents.subList(fromIndex, toIndex);
}
- class Iter implements ListIterator<MotionEvent> {
+ class Iter implements ListIterator<T> {
- private final ListIterator<MotionEvent> mIterator;
+ private final ListIterator<T> mIterator;
Iter(int index) {
- this.mIterator = mMotionEvents.listIterator(index);
+ this.mIterator = mInputEvents.listIterator(index);
}
@Override
@@ -200,7 +200,7 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public MotionEvent next() {
+ public T next() {
return mIterator.next();
}
@@ -210,7 +210,7 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public MotionEvent previous() {
+ public T previous() {
return mIterator.previous();
}
@@ -230,12 +230,12 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
}
@Override
- public void set(MotionEvent motionEvent) {
+ public void set(T inputEvent) {
throw new UnsupportedOperationException();
}
@Override
- public void add(MotionEvent element) {
+ public void add(T element) {
throw new UnsupportedOperationException();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index bb201b624a71..a43447f7fcf4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -23,14 +23,20 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.pm.PackageManager;
+import android.graphics.Insets;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.text.Editable;
import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.systemui.res.R;
/**
@@ -53,6 +59,24 @@ public class EditTextActivity extends Activity
mEditText = findViewById(R.id.edit_text);
mAttribution = findViewById(R.id.attribution);
mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class));
+
+ findViewById(R.id.editor_root).setOnApplyWindowInsetsListener(
+ new View.OnApplyWindowInsetsListener() {
+ @NonNull
+ @Override
+ public WindowInsets onApplyWindowInsets(@NonNull View view,
+ @NonNull WindowInsets windowInsets) {
+ Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) view.getLayoutParams();
+ layoutParams.leftMargin = insets.left;
+ layoutParams.bottomMargin = insets.bottom;
+ layoutParams.rightMargin = insets.right;
+ layoutParams.topMargin = insets.top;
+ view.setLayoutParams(layoutParams);
+ return WindowInsets.CONSUMED;
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index ce798ba3912f..f7ea25c8c0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.ui.view
import android.view.View
+import kotlinx.coroutines.DisposableHandle
/**
* Set this view's [View#importantForAccessibility] to [View#IMPORTANT_FOR_ACCESSIBILITY_YES] or
@@ -43,3 +44,27 @@ inline fun <reified T : View> View.getNearestParent(): T? {
}
return null
}
+
+/** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle =
+ onLayoutChanged { v, _, _, _, _, _, _, _, _ ->
+ onLayoutChanged(v)
+ }
+
+/** Adds the [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(listener: View.OnLayoutChangeListener): DisposableHandle {
+ addOnLayoutChangeListener(listener)
+ return DisposableHandle { removeOnLayoutChangeListener(listener) }
+}
+
+/** Adds a [View.OnApplyWindowInsetsListener] and provides a [DisposableHandle] for teardown. */
+fun View.onApplyWindowInsets(listener: View.OnApplyWindowInsetsListener): DisposableHandle {
+ setOnApplyWindowInsetsListener(listener)
+ return DisposableHandle { setOnApplyWindowInsetsListener(null) }
+}
+
+/** Adds a [View.OnTouchListener] and provides a [DisposableHandle] for teardown. */
+fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle {
+ setOnTouchListener(listener)
+ return DisposableHandle { setOnTouchListener(null) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 0e9b32ffd12a..40d744015498 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.communal.data.repository
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.UserInfo
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
@@ -30,15 +33,18 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -65,14 +71,36 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ broadcastDispatcher: BroadcastDispatcher,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
override val isCtaDismissed: Flow<Boolean> =
- userRepository.selectedUserInfo
+ combine(
+ userRepository.selectedUserInfo,
+ // Make sure combine can emit even if we never get a Backup & Restore event,
+ // which is the most common case as restoration only happens on initial device
+ // setup.
+ backupRestorationEvents.emitOnStart().onEach {
+ logger.i("Restored state for communal preferences.")
+ },
+ ) { user, _ ->
+ user
+ }
.flatMapLatest(::observeCtaDismissState)
.logDiffsForTable(
tableLogBuffer = tableLogBuffer,
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 7c65d21e2814..c724244816ea 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
@@ -21,6 +21,7 @@ import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.appwidget.AppWidgetProviderInfo
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.model.CommunalEnabledState
@@ -116,12 +117,12 @@ constructor(
private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
secureSettings
- .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_ENABLED))
+ .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
// Force an update
.onStart { emit(Unit) }
.map {
secureSettings.getIntForUser(
- GLANCEABLE_HUB_ENABLED,
+ Settings.Secure.GLANCEABLE_HUB_ENABLED,
ENABLED_SETTING_DEFAULT,
user.id,
) == 1
@@ -138,7 +139,6 @@ constructor(
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
companion object {
- const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
private const val ENABLED_SETTING_DEFAULT = 1
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt
new file mode 100644
index 000000000000..55c6ec88bd11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Helper to backup & restore the shared preferences in glanceable hub for the current user. */
+class CommunalPrefsBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ UserFileManagerImpl.createFile(
+ userId = userId,
+ fileName = FILE_NAME,
+ )
+ .path
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1ed4b503b43d..7d86e0639b16 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,6 +43,7 @@ import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
import com.android.systemui.bouncer.data.repository.BouncerRepositoryModule;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
import com.android.systemui.bouncer.ui.BouncerViewModule;
+import com.android.systemui.brightness.dagger.ScreenBrightnessModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
@@ -229,6 +230,7 @@ import kotlinx.coroutines.CoroutineScope;
RecordIssueModule.class,
ReferenceModule.class,
RetailModeModule.class,
+ ScreenBrightnessModule.class,
ScreenshotModule.class,
SensorModule.class,
SecurityRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index baae986c494d..0e04d15d8680 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,7 +40,6 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -313,11 +312,7 @@ constructor(
// or device starts going to sleep.
merge(
powerInteractor.isAsleep,
- if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
- } else {
- keyguardRepository.keyguardDoneAnimationsFinished.map { true }
- },
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE),
userRepository.selectedUser.map {
it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
},
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 82834387f7b8..f779ac8fb0bb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -1,15 +1,11 @@
package com.android.systemui.deviceentry.data.repository
-import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
import dagger.Module
@@ -17,38 +13,20 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Interface for classes that can access device-entry-related application state. */
interface DeviceEntryRepository {
/**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None); in such cases, the value of this
- * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
- * by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean>
-
- /**
* Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
* chosen any secure authentication method and even if they set the lockscreen to be dismissed
* when the user swipes on it.
*/
suspend fun isLockscreenEnabled(): Boolean
- /** Report successful authentication for device entry. */
- fun reportSuccessfulAuthentication()
-
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed.
@@ -73,53 +51,8 @@ constructor(
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
private val keyguardBypassController: KeyguardBypassController,
- keyguardStateController: KeyguardStateController,
- keyguardRepository: KeyguardRepository,
) : DeviceEntryRepository {
- private val _isUnlocked = MutableStateFlow(false)
-
- private val isUnlockedReportedByLegacyKeyguard =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardStateController.Callback {
- override fun onUnlockedChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isUnlocked due to onUnlockedChanged"
- )
- }
-
- override fun onKeyguardShowingChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isUnlocked due to onKeyguardShowingChanged"
- )
- }
- }
-
- keyguardStateController.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "initial isKeyguardUnlocked"
- )
-
- awaitClose { keyguardStateController.removeCallback(callback) }
- }
- .distinctUntilChanged()
- .onEach { _isUnlocked.value = it }
- .stateIn(
- applicationScope,
- SharingStarted.Eagerly,
- initialValue = false,
- )
-
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
val selectedUserId = userRepository.getSelectedUserInfo().id
@@ -127,11 +60,6 @@ constructor(
}
}
- override fun reportSuccessfulAuthentication() {
- Log.d(TAG, "Successful authentication reported.")
- _isUnlocked.value = true
- }
-
override val isBypassEnabled: StateFlow<Boolean> =
conflatedCallbackFlow {
val listener =
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index c4e0ef7d082d..ec574d2d031d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthentication
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -58,6 +59,9 @@ constructor(
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+ val fingerprintSuccess: Flow<SuccessFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<SuccessFingerprintAuthenticationStatus>()
+
/**
* Whether fingerprint authentication is currently allowed for the user. This is true if the
* user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index fa2421a3516d..5c1ca646529e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -26,7 +26,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
@@ -35,14 +34,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -65,8 +61,7 @@ constructor(
private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val trustInteractor: TrustInteractor,
- flags: SceneContainerFlags,
- deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
@@ -78,7 +73,14 @@ constructor(
* of this flow will always be `true`, even if the lockscreen is showing and still needs to be
* dismissed by the user to proceed.
*/
- val isUnlocked: StateFlow<Boolean> = deviceUnlockedInteractor.isDeviceUnlocked
+ val isUnlocked: StateFlow<Boolean> =
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .map { it.isUnlocked }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked,
+ )
/**
* Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
@@ -100,17 +102,6 @@ constructor(
)
/**
- * Whether the user is currently authenticated by a TrustAgent like trusted device, location,
- * etc., or by face auth.
- */
- private val isPassivelyAuthenticated =
- merge(
- trustInteractor.isTrusted,
- faceAuthInteractor.authenticated,
- )
- .onStart { emit(false) }
-
- /**
* Whether it's currently possible to swipe up to enter the device without requiring
* authentication or when the device is already authenticated using a passive authentication
* mechanism like face or trust manager. This returns `false` whenever the lockscreen has been
@@ -129,10 +120,13 @@ constructor(
authenticationInteractor.authenticationMethod.map {
it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
},
- isPassivelyAuthenticated,
+ deviceUnlockedInteractor.deviceUnlockStatus,
isDeviceEntered
- ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered ->
- (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered
+ ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered ->
+ (isSwipeAuthMethod ||
+ (deviceUnlockStatus.isUnlocked &&
+ deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
+ !isDeviceEntered
}
.stateIn(
scope = applicationScope,
@@ -235,7 +229,8 @@ constructor(
* `false` if the device can be entered without authenticating first.
*/
suspend fun isAuthenticationRequired(): Boolean {
- return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure
+ return !deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked &&
+ authenticationInteractor.getAuthenticationMethod().isSecure
}
/**
@@ -246,18 +241,6 @@ constructor(
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
- init {
- if (flags.isEnabled()) {
- applicationScope.launch {
- authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful ->
- if (isSuccessful) {
- repository.reportSuccessfulAuthentication()
- }
- }
- }
- }
- }
-
private val wasRebootedForMainlineUpdate
get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index b0495fb8e819..098ede30d618 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -21,13 +21,23 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceUnlockedInteractor
@Inject
@@ -35,28 +45,63 @@ constructor(
@Application private val applicationScope: CoroutineScope,
authenticationInteractor: AuthenticationInteractor,
deviceEntryRepository: DeviceEntryRepository,
+ trustInteractor: TrustInteractor,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val powerInteractor: PowerInteractor,
) {
+ private val deviceUnlockSource =
+ merge(
+ fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint },
+ faceAuthInteractor.authenticated
+ .filter { it }
+ .map {
+ if (deviceEntryRepository.isBypassEnabled.value) {
+ DeviceUnlockSource.FaceWithBypass
+ } else {
+ DeviceUnlockSource.FaceWithoutBypass
+ }
+ },
+ trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
+ authenticationInteractor.onAuthenticationResult
+ .filter { it }
+ .map { DeviceUnlockSource.BouncerInput }
+ )
+
/**
- * Whether the device is unlocked.
+ * Whether the device is unlocked or not, along with the information about the authentication
+ * method that was used to unlock the device.
*
* A device that is not yet unlocked requires unlocking by completing an authentication
* challenge according to the current authentication method, unless in cases when the current
* authentication method is not "secure" (for example, None and Swipe); in such cases, the value
- * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
- * dismissed by the user to proceed.
+ * of this flow will always be an instance of [DeviceUnlockStatus] with
+ * [DeviceUnlockStatus.deviceUnlockSource] as null and [DeviceUnlockStatus.isUnlocked] set to
+ * true, even if the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
- val isDeviceUnlocked: StateFlow<Boolean> =
- combine(
- deviceEntryRepository.isUnlocked,
- authenticationInteractor.authenticationMethod,
- ) { isUnlocked, authenticationMethod ->
- (!authenticationMethod.isSecure || isUnlocked) &&
- authenticationMethod != AuthenticationMethodModel.Sim
+ val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
+ authenticationInteractor.authenticationMethod
+ .flatMapLatest { authMethod ->
+ if (!authMethod.isSecure) {
+ flowOf(DeviceUnlockStatus(true, null))
+ } else if (authMethod == AuthenticationMethodModel.Sim) {
+ // Device is locked if SIM is locked.
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ powerInteractor.isAsleep.flatMapLatest { isAsleep ->
+ if (isAsleep) {
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ }
+ }
+ }
}
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
- initialValue = false,
+ initialValue = DeviceUnlockStatus(false, null),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt
new file mode 100644
index 000000000000..619c2400ec72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/**
+ * Source of the device unlock.
+ *
+ * @property dismissesLockscreen whether unlock with this authentication method dismisses the
+ * lockscreen and enters the device.
+ */
+sealed class DeviceUnlockSource(val dismissesLockscreen: Boolean) {
+
+ data object Fingerprint : DeviceUnlockSource(true)
+ data object FaceWithBypass : DeviceUnlockSource(dismissesLockscreen = true)
+ data object FaceWithoutBypass : DeviceUnlockSource(dismissesLockscreen = false)
+ data object TrustAgent : DeviceUnlockSource(dismissesLockscreen = false)
+ data object BouncerInput : DeviceUnlockSource(dismissesLockscreen = true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt
new file mode 100644
index 000000000000..f694c331bd7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/**
+ * Wrapper class that combines whether device is unlocked or not, along with the authentication
+ * method used to unlock the device.
+ *
+ * @property isUnlocked whether device is unlocked or not.
+ * @property deviceUnlockSource source that unlocked the device, null if lockscreen is not secure or
+ * if [isUnlocked] is false.
+ */
+data class DeviceUnlockStatus(
+ val isUnlocked: Boolean,
+ val deviceUnlockSource: DeviceUnlockSource?
+) {
+ init {
+ assert(isUnlocked || deviceUnlockSource == null) {
+ "deviceUnlockSource must be null when device is locked."
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
index f5a88708ee2a..191d612d3668 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -54,9 +54,21 @@ abstract class UdfpsAccessibilityOverlayViewModel(
fun onHoverEvent(v: View, event: MotionEvent): Boolean {
val overlayParams = udfpsOverlayParams.value
val scaledTouch: Point =
- udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams)
+ udfpsUtils.getTouchInNativeCoordinates(
+ event.getPointerId(0),
+ event,
+ overlayParams, /* rotateToPortrait */
+ false
+ )
- if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) {
+ if (
+ !udfpsUtils.isWithinSensorArea(
+ event.getPointerId(0),
+ event,
+ overlayParams,
+ /* rotateTouchToPortrait */ false
+ )
+ ) {
// view only receives motionEvents when [visible] which requires touchExplorationEnabled
val announceStr =
udfpsUtils.onTouchOutsideOfSensorArea(
@@ -65,6 +77,7 @@ abstract class UdfpsAccessibilityOverlayViewModel(
scaledTouch.x,
scaledTouch.y,
overlayParams,
+ /* touchRotatedToPortrait */ false
)
if (announceStr != null) {
v.announceForAccessibility(announceStr)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 424bd0a3e23b..9a9e698e0138 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -209,6 +209,15 @@ public class DozeLog implements Dumpable {
}
/**
+ * Logs cancelation requests for time ticks
+ * @param isPending is an unschedule request pending?
+ * @param isTimeTickScheduled is a time tick request scheduled
+ */
+ public void tracePendingUnscheduleTimeTick(boolean isPending, boolean isTimeTickScheduled) {
+ mLogger.logPendingUnscheduleTimeTick(isPending, isTimeTickScheduled);
+ }
+
+ /**
* Appends keyguard visibility change event to the logs
* @param showing whether the keyguard is now showing
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 75b8e513c14a..9d6693efffa3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -162,6 +162,15 @@ class DozeLogger @Inject constructor(
})
}
+ fun logPendingUnscheduleTimeTick(isPending: Boolean, isTimeTickScheduled: Boolean) {
+ buffer.log(TAG, INFO, {
+ bool1 = isPending
+ bool2 = isTimeTickScheduled
+ }, {
+ "Pending unschedule time tick, isPending=$bool1, isTimeTickScheduled:$bool2"
+ })
+ }
+
fun logDozeStateChanged(state: DozeMachine.State) {
buffer.log(TAG, INFO, {
str1 = state.name
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 34a80e867153..1a855d735a02 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,11 +26,12 @@ import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.Log;
-import com.android.systemui.DejankUtils;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.wakelock.WakeLock;
import java.util.Calendar;
@@ -52,14 +53,19 @@ public class DozeUi implements DozeMachine.Part {
private final boolean mCanAnimateTransition;
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
+ private final DelayableExecutor mBgExecutor;
+ private Runnable mCancelRunnable = null;
private long mLastTimeTickElapsed = 0;
// If time tick is scheduled and there's not a pending runnable to cancel:
- private boolean mTimeTickScheduled;
+ private volatile boolean mTimeTickScheduled;
private final Runnable mCancelTimeTickerRunnable = new Runnable() {
@Override
public void run() {
- mTimeTicker.cancel();
+ mDozeLog.tracePendingUnscheduleTimeTick(false, mTimeTickScheduled);
+ if (!mTimeTickScheduled) {
+ mTimeTicker.cancel();
+ }
}
};
@@ -67,11 +73,13 @@ public class DozeUi implements DozeMachine.Part {
public DozeUi(Context context, AlarmManager alarmManager,
WakeLock wakeLock, DozeHost host, @Main Handler handler,
DozeParameters params,
+ @Background DelayableExecutor bgExecutor,
DozeLog dozeLog) {
mContext = context;
mWakeLock = wakeLock;
mHost = host;
mHandler = handler;
+ mBgExecutor = bgExecutor;
mCanAnimateTransition = !params.getDisplayNeedsBlanking();
mDozeParameters = params;
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
@@ -166,7 +174,6 @@ public class DozeUi implements DozeMachine.Part {
return;
}
mTimeTickScheduled = true;
- DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
long time = System.currentTimeMillis();
long delta = roundToNextMinute(time) - System.currentTimeMillis();
@@ -182,7 +189,8 @@ public class DozeUi implements DozeMachine.Part {
return;
}
mTimeTickScheduled = false;
- DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
+ mDozeLog.tracePendingUnscheduleTimeTick(true, mTimeTickScheduled);
+ mBgExecutor.execute(mCancelTimeTickerRunnable);
}
private void verifyLastTimeTick() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 926f7f1aee48..75c50fd5f586 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.touch;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -81,6 +82,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
private final LockPatternUtils mLockPatternUtils;
private final UserTracker mUserTracker;
private final float mBouncerZoneScreenPercentage;
+ private final float mMinBouncerZoneScreenPercentage;
private final ScrimManager mScrimManager;
private ScrimController mCurrentScrimController;
@@ -222,6 +224,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
@Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
FlingAnimationUtils flingAnimationUtilsClosing,
@Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+ @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
UiEventLogger uiEventLogger) {
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
@@ -229,6 +232,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
mLockPatternUtils = lockPatternUtils;
mUserTracker = userTracker;
mBouncerZoneScreenPercentage = swipeRegionPercentage;
+ mMinBouncerZoneScreenPercentage = minRegionPercentage;
mFlingAnimationUtils = flingAnimationUtils;
mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
mValueAnimatorCreator = valueAnimatorCreator;
@@ -237,24 +241,27 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final int width = bounds.width();
final int height = bounds.height();
-
- if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
- region.op(new Rect(0, 0, width,
- Math.round(
- height * mBouncerZoneScreenPercentage)),
- Region.Op.UNION);
- } else {
- region.op(new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width,
- height),
- Region.Op.UNION);
+ final float minBouncerHeight = height * mMinBouncerZoneScreenPercentage;
+ final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
+
+ final boolean isBouncerShowing =
+ mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false);
+ final Rect normalRegion = isBouncerShowing
+ ? new Rect(0, 0, width, Math.round(height * mBouncerZoneScreenPercentage))
+ : new Rect(0, Math.round(height * (1 - mBouncerZoneScreenPercentage)),
+ width, height);
+
+ if (!isBouncerShowing && exclusionRect != null) {
+ int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
+ normalRegion.top = Math.max(normalRegion.top, lowestBottom);
}
+ region.union(normalRegion);
}
+
@Override
public void onSessionStart(TouchSession session) {
mVelocityTracker = mVelocityTrackerFactory.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index e5c705f608b9..13588c2d45fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -87,7 +87,7 @@ public class CommunalTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 55a9c0c4de99..3b22b31de121 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -18,9 +18,15 @@ package com.android.systemui.dreams.touch;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.systemui.shared.Flags.bouncerAreaExclusion;
+
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -31,6 +37,8 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -58,8 +66,23 @@ import javax.inject.Inject;
public class DreamOverlayTouchMonitor {
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
- private final Executor mExecutor;
+ public String TAG = "DreamOverlayTouchMonitor";
+ private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
private final Lifecycle mLifecycle;
+ private Rect mExclusionRect = null;
+
+ private ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion,
+ Region systemGestureExclusionUnrestricted) {
+ mExclusionRect = systemGestureExclusion.getBounds();
+ }
+ };
+
+
/**
* Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
@@ -67,7 +90,7 @@ public class DreamOverlayTouchMonitor {
private ListenableFuture<DreamTouchHandler.TouchSession> push(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
+ mMainExecutor.execute(() -> {
if (!mActiveTouchSessions.remove(touchSessionImpl)) {
completer.set(null);
return;
@@ -90,7 +113,7 @@ public class DreamOverlayTouchMonitor {
private ListenableFuture<DreamTouchHandler.TouchSession> pop(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
+ mMainExecutor.execute(() -> {
if (mActiveTouchSessions.remove(touchSessionImpl)) {
touchSessionImpl.onRemoved();
@@ -240,6 +263,17 @@ public class DreamOverlayTouchMonitor {
*/
private void startMonitoring() {
stopMonitoring(true);
+ if (bouncerAreaExclusion()) {
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mWindowManagerService.registerSystemGestureExclusionListener(
+ mGestureExclusionListener, mDisplayId);
+ } catch (RemoteException e) {
+ // Handle the exception
+ Log.e(TAG, "Failed to register gesture exclusion listener", e);
+ }
+ });
+ }
mCurrentInputSession = mInputSessionFactory.create(
"dreamOverlay",
mInputEventListener,
@@ -252,6 +286,18 @@ public class DreamOverlayTouchMonitor {
* Destroys any active {@link InputSession}.
*/
private void stopMonitoring(boolean force) {
+ mExclusionRect = null;
+ if (bouncerAreaExclusion()) {
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mWindowManagerService.unregisterSystemGestureExclusionListener(
+ mGestureExclusionListener, mDisplayId);
+ } catch (RemoteException e) {
+ // Handle the exception
+ Log.e(TAG, "unregisterSystemGestureExclusionListener: failed", e);
+ }
+ });
+ }
if (mCurrentInputSession == null) {
return;
}
@@ -263,7 +309,7 @@ public class DreamOverlayTouchMonitor {
// When we stop monitoring touches, we must ensure that all active touch sessions and
// descendants informed of the removal so any cleanup for active tracking can proceed.
- mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
+ mMainExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
while (touchSession != null) {
touchSession.onRemoved();
touchSession = touchSession.getPredecessor();
@@ -295,11 +341,15 @@ public class DreamOverlayTouchMonitor {
if (!handler.isEnabled()) {
continue;
}
- final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
- TYPE_APPLICATION_OVERLAY);
-
- final Region initiationRegion = Region.obtain();
- handler.getTouchInitiationRegion(maxBounds, initiationRegion);
+ final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
+ TYPE_APPLICATION_OVERLAY);
+ final Region initiationRegion = Region.obtain();
+ Rect exclusionRect = null;
+ if (bouncerAreaExclusion()) {
+ exclusionRect = getCurrentExclusionRect();
+ }
+ handler.getTouchInitiationRegion(
+ maxBounds, initiationRegion, exclusionRect);
if (!initiationRegion.isEmpty()) {
// Initiation regions require a motion event to determine pointer location
@@ -335,6 +385,9 @@ public class DreamOverlayTouchMonitor {
.flatMap(Collection::stream)
.forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
}
+ private Rect getCurrentExclusionRect() {
+ return mExclusionRect;
+ }
};
/**
@@ -416,6 +469,9 @@ public class DreamOverlayTouchMonitor {
private InputSessionComponent.Factory mInputSessionFactory;
private InputSession mCurrentInputSession;
+ private final int mDisplayId;
+ private final IWindowManager mWindowManagerService;
+
/**
* Designated constructor for {@link DreamOverlayTouchMonitor}
@@ -432,15 +488,21 @@ public class DreamOverlayTouchMonitor {
@Inject
public DreamOverlayTouchMonitor(
@Main Executor executor,
+ @Background Executor backgroundExecutor,
Lifecycle lifecycle,
InputSessionComponent.Factory inputSessionFactory,
DisplayHelper displayHelper,
- Set<DreamTouchHandler> handlers) {
+ Set<DreamTouchHandler> handlers,
+ IWindowManager windowManagerService,
+ @DisplayId int displayId) {
+ mDisplayId = displayId;
mHandlers = handlers;
mInputSessionFactory = inputSessionFactory;
- mExecutor = executor;
+ mMainExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mLifecycle = lifecycle;
mDisplayHelper = displayHelper;
+ mWindowManagerService = windowManagerService;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
index 72ad45d1055c..1ec000835ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
@@ -104,7 +104,7 @@ public interface DreamTouchHandler {
* indicating the entire screen should be considered.
* @param region A {@link Region} that is passed in to the target entry touch region.
*/
- default void getTouchInitiationRegion(Rect bounds, Region region) {
+ default void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
index 6f05e83b22ba..e0bf52e81875 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -82,7 +82,7 @@ public class ShadeTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index 8cf11a9817b7..a5db2ff81f99 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -21,10 +21,10 @@ import android.content.res.Resources;
import android.util.TypedValue;
import android.view.VelocityTracker;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -46,6 +46,9 @@ public class BouncerSwipeModule {
*/
public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+ public static final String MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE =
+ "min_bouncer_zone_screen_percentage";
+
/**
* The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
*/
@@ -110,6 +113,18 @@ public class BouncerSwipeModule {
}
/**
+ * Provides the minimum region to start wipe gestures from.
+ */
+ @Provides
+ @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ public static float providesMinBouncerZoneScreenPercentage(@Main Resources resources) {
+ TypedValue typedValue = new TypedValue();
+ resources.getValue(R.dimen.dream_overlay_bouncer_min_region_screen_percentage,
+ typedValue, true);
+ return typedValue.getFloat();
+ }
+
+ /**
* Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
* a wrapper around {@link ValueAnimator}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 73878b6780d9..640534cc9d34 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -410,13 +410,6 @@ object Flags {
val CLIPBOARD_SHARED_TRANSITIONS =
unreleasedFlag("clipboard_shared_transitions", teamfood = true)
- /**
- * Whether the compose bouncer is enabled. This ensures ProGuard can
- * remove unused code from our APK at compile time.
- */
- // TODO(b/280877228): Tracking Bug
- @JvmField val COMPOSE_BOUNCER_ENABLED = false
-
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 30beca7d00e1..e6e6ff6dcadb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,6 +41,7 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -72,6 +73,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -108,6 +110,7 @@ constructor(
private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
private val clockInteractor: KeyguardClockInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -205,12 +208,13 @@ constructor(
chipbarCoordinator,
screenOffAnimationController,
shadeInteractor,
- { keyguardStatusViewController!!.getClockController() },
+ clockInteractor,
interactionJankMonitor,
deviceEntryHapticsInteractor,
vibratorHelper,
falsingManager,
keyguardViewMediator,
+ mainImmediateDispatcher,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 53aee5da0793..654610e8cae8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -178,8 +178,6 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -189,6 +187,7 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -964,6 +963,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@VisibleForTesting
final ActivityTransitionAnimator.Controller mOccludeAnimationController =
new ActivityTransitionAnimator.Controller() {
+ private boolean mIsLaunching = true;
+
+ @Override
+ public boolean isLaunching() {
+ return mIsLaunching;
+ }
+
@Override
public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
@@ -2147,13 +2153,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
synchronized (KeyguardViewMediator.this) {
- if (mHiding && isOccluded) {
- // We're in the process of going away but WindowManager wants to show a
- // SHOW_WHEN_LOCKED activity instead.
- // TODO(bc-unlock): Migrate to remote animation.
- startKeyguardExitAnimation(0, 0);
- }
-
mPowerGestureIntercepted =
isOccluded && mUpdateMonitor.isSecureCameraLaunchedOverKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
index aab90c378a19..585bd6adf11f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -263,6 +263,7 @@ constructor(
@VisibleForTesting
val occludeAnimationController: ActivityTransitionAnimator.Controller =
object : ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
override var transitionContainer: ViewGroup
get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 8cc077992f7f..4c54bfd3a17c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -22,6 +22,7 @@ import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.FloatRange
import android.os.Trace
import android.util.Log
+import com.android.app.tracing.coroutines.withContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -41,7 +42,6 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.withContext
/**
* The source of truth for all keyguard transitions.
@@ -150,7 +150,7 @@ constructor(
_currentTransitionInfo.value = info
// Animators must be started on the main thread.
- return withContext(mainDispatcher) {
+ return withContext("$TAG#startTransition", mainDispatcher) {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return@withContext null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index e017129bd5c5..bf1f07479f34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -26,6 +26,9 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -33,6 +36,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
@@ -48,6 +52,8 @@ constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val sceneContainerFlags: SceneContainerFlags,
+ private val sceneInteractor: SceneInteractor,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -65,16 +71,35 @@ constructor(
}
}
+ private val isSideFpsIndicatorOnPrimaryBouncerEnabled: Boolean
+ get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+
+ private val isBouncerSceneActive: Flow<Boolean> =
+ if (sceneContainerFlags.isEnabled()) {
+ sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged()
+ } else {
+ flowOf(false)
+ }
+
private val showIndicatorForPrimaryBouncer: Flow<Boolean> =
merge(
+ // Legacy bouncer visibility changes.
primaryBouncerInteractor.isShowing,
primaryBouncerInteractor.startingToHide,
primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(),
+ // Bouncer scene visibility changes.
+ isBouncerSceneActive,
deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it }
)
- .map { shouldShowIndicatorForPrimaryBouncer() }
+ .map {
+ isBouncerActive() &&
+ isSideFpsIndicatorOnPrimaryBouncerEnabled &&
+ keyguardUpdateMonitor.isFingerprintDetectionRunning &&
+ keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+ }
private val showIndicatorForAlternateBouncer: Flow<Boolean> =
+ // Note: this interactor internally verifies that SideFPS is enabled and running.
alternateBouncerInteractor.isVisible
/**
@@ -89,16 +114,11 @@ constructor(
}
.distinctUntilChanged()
- private fun shouldShowIndicatorForPrimaryBouncer(): Boolean {
- val sfpsEnabled: Boolean =
- context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
- val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
- val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
-
+ private fun isBouncerActive(): Boolean {
+ if (sceneContainerFlags.isEnabled()) {
+ return sceneInteractor.currentScene.value == Scenes.Bouncer
+ }
return primaryBouncerInteractor.isBouncerShowing() &&
- sfpsEnabled &&
- sfpsDetectionRunning &&
- isUnlockingWithFpAllowed &&
!primaryBouncerInteractor.isAnimatingAway()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 4bf5200a507f..f359525fbe89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -33,7 +34,6 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.launch
@SysUISingleton
class FromAodTransitionInteractor
@@ -72,7 +72,7 @@ constructor(
// Use PowerInteractor's wakefulness, which is the earliest wake signal available. We
// have all of the information we need at this time to make a decision about where to
// transition.
- scope.launch {
+ scope.launch("$TAG#listenForAodToAwake") {
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { wakefulness -> wakefulness.isAwake() }
.sample(
@@ -150,7 +150,7 @@ constructor(
return
}
- scope.launch {
+ scope.launch("$TAG#listenForAodToOccluded") {
keyguardInteractor.isKeyguardOccluded
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect {
@@ -168,7 +168,7 @@ constructor(
* PRIMARY_BOUNCER.
*/
private fun listenForAodToPrimaryBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForAodToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing }
.collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
@@ -181,7 +181,7 @@ constructor(
return
}
- scope.launch {
+ scope.launch("$TAG#listenForAodToGone") {
powerInteractor.isAwake
.debounce(50L)
.filterRelevantKeyguardState()
@@ -209,7 +209,7 @@ constructor(
* AOD.
*/
fun dismissAod() {
- scope.launch { startTransitionTo(KeyguardState.GONE) }
+ scope.launch("$TAG#dismissAod") { startTransitionTo(KeyguardState.GONE) }
}
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
@@ -224,7 +224,7 @@ constructor(
}
companion object {
- const val TAG = "FromAodTransitionInteractor"
+ private const val TAG = "FromAodTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_LOCKSCREEN_DURATION = 500.milliseconds
val TO_GONE_DURATION = DEFAULT_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 8d7c964cc71d..bef5ee507128 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -35,7 +35,9 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
@SysUISingleton
@@ -106,6 +108,7 @@ constructor(
}
}
+ @OptIn(FlowPreview::class)
private fun listenForDreamingToOccluded() {
if (KeyguardWmStateRefactor.isEnabled) {
scope.launch {
@@ -121,13 +124,24 @@ constructor(
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isDreaming,
+ keyguardInteractor.isDreaming
+ // Debounce the dreaming signal since there is a race condition between
+ // the occluded and dreaming signals. We therefore add a small delay
+ // to give enough time for occluded to flip to false when the dream
+ // ends, to avoid transitioning to OCCLUDED erroneously when exiting
+ // the dream.
+ .debounce(100.milliseconds),
::Pair
)
.filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
isOccluded && !isDreaming
}
- .collect { startTransitionTo(KeyguardState.OCCLUDED) }
+ .collect {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ ownerReason = "Occluded but no longer dreaming",
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 4a8818207568..c2c095bb9574 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -68,13 +69,13 @@ constructor(
}
fun showKeyguard() {
- scope.launch { startTransitionTo(KeyguardState.LOCKSCREEN) }
+ scope.launch("$TAG#showKeyguard") { startTransitionTo(KeyguardState.LOCKSCREEN) }
}
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
+ scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
biometricSettingsRepository.isCurrentUserInLockdown
.distinctUntilChanged()
.filterRelevantKeyguardStateAnd { inLockdown -> inLockdown }
@@ -90,7 +91,7 @@ constructor(
}
}
} else {
- scope.launch {
+ scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
keyguardInteractor.isKeyguardShowing
.filterRelevantKeyguardStateAnd { isKeyguardShowing -> isKeyguardShowing }
.sample(communalInteractor.isIdleOnCommunal, ::Pair)
@@ -108,7 +109,7 @@ constructor(
}
private fun listenForGoneToDreamingLockscreenHosted() {
- scope.launch {
+ scope.launch("$TAG#listenForGoneToDreamingLockscreenHosted") {
keyguardInteractor.isActiveDreamLockscreenHosted
.filterRelevantKeyguardStateAnd { isActiveDreamLockscreenHosted ->
isActiveDreamLockscreenHosted
@@ -118,7 +119,7 @@ constructor(
}
private fun listenForGoneToDreaming() {
- scope.launch {
+ scope.launch("$TAG#listenForGoneToDreaming") {
keyguardInteractor.isAbleToDream
.sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair)
.filterRelevantKeyguardStateAnd { (isAbleToDream, isActiveDreamLockscreenHosted) ->
@@ -129,7 +130,7 @@ constructor(
}
private fun listenForGoneToAodOrDozing() {
- scope.launch {
+ scope.launch("$TAG#listenForGoneToAodOrDozing") {
listenForSleepTransition(
modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET },
)
@@ -151,6 +152,7 @@ constructor(
}
companion object {
+ private const val TAG = "FromGoneTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_AOD_DURATION = 1300.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index b35faf753ba9..56261e0865e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -119,7 +120,7 @@ constructor(
}
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToDreaming") {
keyguardInteractor.isAbleToDream
.filterRelevantKeyguardState()
.sampleCombine(
@@ -149,7 +150,7 @@ constructor(
}
private fun listenForLockscreenToPrimaryBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
.collect {
@@ -162,7 +163,7 @@ constructor(
}
private fun listenForLockscreenToAlternateBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
keyguardInteractor.alternateBouncerShowing
.filterRelevantKeyguardStateAnd { isAlternateBouncerShowing ->
isAlternateBouncerShowing
@@ -174,7 +175,7 @@ constructor(
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.legacyShadeExpansion
.sampleCombine(
startedKeyguardTransitionStep,
@@ -258,7 +259,7 @@ constructor(
}
fun dismissKeyguard() {
- scope.launch { startTransitionTo(KeyguardState.GONE) }
+ scope.launch("$TAG#dismissKeyguard") { startTransitionTo(KeyguardState.GONE) }
}
private fun listenForLockscreenToGone() {
@@ -266,7 +267,7 @@ constructor(
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGone") {
keyguardInteractor.isKeyguardGoingAway
.filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
.collect {
@@ -281,7 +282,7 @@ constructor(
private fun listenForLockscreenToGoneDragging() {
if (KeyguardWmStateRefactor.isEnabled) {
// When the refactor is enabled, we no longer use isKeyguardGoingAway.
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGoneDragging") {
swipeToDismissInteractor.dismissFling
.filterNotNull()
.filterRelevantKeyguardState()
@@ -292,7 +293,7 @@ constructor(
private fun listenForLockscreenToOccludedOrDreaming() {
if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
keyguardOcclusionInteractor.showWhenLockedActivityInfo
.filterRelevantKeyguardStateAnd { it.isOnTop }
.collect { taskInfo ->
@@ -306,7 +307,7 @@ constructor(
}
}
} else {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
keyguardInteractor.isKeyguardOccluded
.filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
.collect { startTransitionTo(KeyguardState.OCCLUDED) }
@@ -315,7 +316,7 @@ constructor(
}
private fun listenForLockscreenToAodOrDozing() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
listenForSleepTransition(
modeOnCanceledFromStartedStep = { startedStep ->
if (
@@ -367,7 +368,7 @@ constructor(
}
companion object {
- const val TAG = "FromLockscreenTransitionInteractor"
+ private const val TAG = "FromLockscreenTransitionInteractor"
private val DEFAULT_DURATION = 400.milliseconds
val TO_DOZING_DURATION = 500.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 99b691ebd6ae..d551c9b9a4de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.keyguard.domain.interactor
+import android.util.Log
import com.android.keyguard.ClockEventController
+import com.android.keyguard.KeyguardClockSwitch
import com.android.keyguard.KeyguardClockSwitch.ClockSize
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
@@ -54,6 +56,15 @@ constructor(
keyguardClockRepository.setClockSize(size)
}
+ val renderedClockId: ClockId
+ get() {
+ return clock?.let { clock -> clock.config.id }
+ ?: run {
+ Log.e(TAG, "No clock is available")
+ KeyguardClockSwitch.MISSING_CLOCK_ID
+ }
+ }
+
fun animateFoldToAod(foldFraction: Float) {
clock?.let { clock ->
clock.smallClock.animations.fold(foldFraction)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 851eafa1a4df..2182fe378d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -49,6 +49,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
@@ -192,7 +193,7 @@ constructor(
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is dismissible or not. */
- val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
+ val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
@Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
@@ -279,12 +280,16 @@ constructor(
* signal should be sent directly to transitions.
*/
val dismissAlpha: Flow<Float?> =
- combine(
- shadeRepository.legacyShadeExpansion,
+ shadeRepository.legacyShadeExpansion
+ .filter { it < 1f }
+ .sampleCombine(
statusBarState,
keyguardTransitionInteractor.currentKeyguardState,
isKeyguardDismissible,
- ) { legacyShadeExpansion, statusBarState, currentKeyguardState, isKeyguardDismissible ->
+ )
+ .map {
+ (legacyShadeExpansion, statusBarState, currentKeyguardState, isKeyguardDismissible)
+ ->
if (
statusBarState == StatusBarState.KEYGUARD &&
isKeyguardDismissible &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 03ed5675a77a..4abd6c6a6453 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -107,7 +107,7 @@ constructor(
*/
val occludingActivityWillDismissKeyguard: StateFlow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- deviceUnlockedInteractor.get().isDeviceUnlocked
+ deviceUnlockedInteractor.get().deviceUnlockStatus.map { it.isUnlocked }
} else {
keyguardInteractor.isKeyguardDismissible
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 68ea5d047e1b..141cca329419 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
@@ -69,9 +70,11 @@ constructor(
}
}
- scope.launch {
- sharedNotificationContainerViewModel.bounds.collect {
- logger.log(TAG, VERBOSE, "Notif: bounds", it)
+ if (!SceneContainerFlag.isEnabled) {
+ scope.launch {
+ sharedNotificationContainerViewModel.bounds.collect {
+ logger.log(TAG, VERBOSE, "Notif: bounds", it)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt
index 9186dde9a9bf..fe5f632c0b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.binder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.keyguard.AuthKeyguardMessageArea
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -36,14 +37,18 @@ object AlternateBouncerMessageAreaViewBinder {
view.setIsVisible(true)
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.message.collect { biometricMsg ->
- if (biometricMsg == null) {
- view.setMessage("", true)
- } else {
- view.setMessage(biometricMsg.message, true)
+ launch("$TAG#viewModel.message") {
+ viewModel.message.collect { biometricMsg ->
+ if (biometricMsg == null) {
+ view.setMessage("", true)
+ } else {
+ view.setMessage(biometricMsg.message, true)
+ }
}
}
}
}
}
+
+ private const val TAG = "AlternateBouncerMessageAreaViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index a861a8782ef9..9dc77d3dc9d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -21,12 +21,12 @@ import android.content.res.ColorStateList
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
object AlternateBouncerUdfpsViewBinder {
@@ -46,13 +46,13 @@ object AlternateBouncerUdfpsViewBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
view.alpha = 0f
- launch {
+ launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
}
}
- launch { viewModel.alpha.collect { view.alpha = it } }
+ launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } }
}
}
@@ -77,13 +77,17 @@ object AlternateBouncerUdfpsViewBinder {
bgView.visibility = View.VISIBLE
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.bgColor") {
viewModel.bgColor.collect { color ->
bgView.imageTintList = ColorStateList.valueOf(color)
}
}
- launch { viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha } }
+ launch("$TAG#viewModel.bgAlpha") {
+ viewModel.bgAlpha.collect { alpha -> bgView.alpha = alpha }
+ }
}
}
}
+
+ private const val TAG = "AlternateBouncerUdfpsViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index 1eea556f235d..53f013275451 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -28,6 +28,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -46,7 +47,6 @@ import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
/**
* When necessary, adds the alternate bouncer window above most other windows (including the
@@ -92,7 +92,7 @@ constructor(
if (!DeviceEntryUdfpsRefactor.isEnabled) {
return
}
- applicationScope.launch {
+ applicationScope.launch("$TAG#alternateBouncerWindowViewModel") {
alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect {
addAlternateBouncerWindowView ->
if (addAlternateBouncerWindowView) {
@@ -186,7 +186,7 @@ constructor(
val tapGestureDetector = alternateBouncerDependencies.tapGestureDetector
view.repeatWhenAttached { alternateBouncerViewContainer ->
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.registerForDismissGestures") {
viewModel.registerForDismissGestures.collect { registerForDismissGestures ->
if (registerForDismissGestures) {
swipeUpAnywhereGestureHandler.addOnGestureDetectedCallback(swipeTag) { _
@@ -205,9 +205,13 @@ constructor(
}
}
- launch { viewModel.scrimAlpha.collect { scrim.viewAlpha = it } }
+ launch("$TAG#viewModel.scrimAlpha") {
+ viewModel.scrimAlpha.collect { scrim.viewAlpha = it }
+ }
- launch { viewModel.scrimColor.collect { scrim.tint = it } }
+ launch("$TAG#viewModel.scrimColor") {
+ viewModel.scrimColor.collect { scrim.tint = it }
+ }
}
}
}
@@ -219,7 +223,7 @@ constructor(
) {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#udfpsIconViewModel.iconLocation") {
udfpsIconViewModel.iconLocation.collect { iconLocation ->
// add UDFPS a11y overlay
val udfpsA11yOverlayViewId =
@@ -292,7 +296,9 @@ constructor(
}
}
}
+ companion object {
+ private const val TAG = "AlternateBouncerViewBinder"
+ private const val swipeTag = "AlternateBouncer-SWIPE"
+ private const val tapTag = "AlternateBouncer-TAP"
+ }
}
-
-private const val swipeTag = "AlternateBouncer-SWIPE"
-private const val tapTag = "AlternateBouncer-TAP"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index f46a207b273a..e423fe0bd8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -25,6 +25,7 @@ import android.view.View
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -34,13 +35,15 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
object DeviceEntryIconViewBinder {
+ private const val TAG = "DeviceEntryIconViewBinder"
+
/**
* Updates UI for:
* - device entry containing view (parent view for the below views)
@@ -58,6 +61,7 @@ object DeviceEntryIconViewBinder {
bgViewModel: DeviceEntryBackgroundViewModel,
falsingManager: FalsingManager,
vibratorHelper: VibratorHelper,
+ mainImmediateDispatcher: CoroutineDispatcher,
) {
DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
val longPressHandlingView = view.longPressHandlingView
@@ -73,31 +77,33 @@ object DeviceEntryIconViewBinder {
view,
HapticFeedbackConstants.CONFIRM,
)
- applicationScope.launch { viewModel.onLongPress() }
+ applicationScope.launch("$TAG#viewModel.onLongPress") {
+ viewModel.onLongPress()
+ }
}
}
- view.repeatWhenAttached {
+ view.repeatWhenAttached(mainImmediateDispatcher) {
// Repeat on CREATED so that the view will always observe the entire
// GONE => AOD transition (even though the view may not be visible until the middle
// of the transition.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#viewModel.isVisible") {
viewModel.isVisible.collect { isVisible ->
longPressHandlingView.isInvisible = !isVisible
}
}
- launch {
+ launch("$TAG#viewModel.isLongPressEnabled") {
viewModel.isLongPressEnabled.collect { isEnabled ->
longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
}
}
- launch {
+ launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
}
}
- launch {
+ launch("$TAG#viewModel.useBackgroundProtection") {
viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
if (useBackgroundProtection) {
bgView.visibility = View.VISIBLE
@@ -106,7 +112,7 @@ object DeviceEntryIconViewBinder {
}
}
}
- launch {
+ launch("$TAG#viewModel.burnInOffsets") {
viewModel.burnInOffsets.collect { burnInOffsets ->
view.translationX = burnInOffsets.x.toFloat()
view.translationY = burnInOffsets.y.toFloat()
@@ -114,15 +120,17 @@ object DeviceEntryIconViewBinder {
}
}
- launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } }
+ launch("$TAG#viewModel.deviceEntryViewAlpha") {
+ viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
+ }
}
}
- fgIconView.repeatWhenAttached {
+ fgIconView.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Start with an empty state
fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
- launch {
+ launch("$TAG#fgViewModel.viewModel") {
fgViewModel.viewModel.collect { viewModel ->
fgIconView.setImageState(
view.getIconState(viewModel.type, viewModel.useAodVariant),
@@ -142,8 +150,10 @@ object DeviceEntryIconViewBinder {
bgView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } }
- launch {
+ launch("$TAG#bgViewModel.alpha") {
+ bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
+ }
+ launch("$TAG#bgViewModel.color") {
bgViewModel.color.collect { color ->
bgView.imageTintList = ColorStateList.valueOf(color)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 7e3ddf92c530..b5d61773d9af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -39,7 +40,6 @@ import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import javax.inject.Inject
import kotlin.math.max
-import kotlinx.coroutines.launch
private const val TAG = "KeyguardBlueprintViewBinder"
private const val DEBUG = false
@@ -90,7 +90,7 @@ constructor(
) {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#viewModel.blueprint") {
viewModel.blueprint.collect { blueprint ->
Trace.beginSection("KeyguardBlueprintViewBinder#applyBlueprint")
val prevBluePrint = viewModel.currentBluePrint
@@ -137,7 +137,7 @@ constructor(
}
}
- launch {
+ launch("$TAG#viewModel.refreshTransition") {
viewModel.refreshTransition.collect { transition ->
Trace.beginSection("KeyguardBlueprintViewBinder#refreshTransition")
val cs =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 397cbe5b3e5d..660a650fb916 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -36,6 +36,7 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.settingslib.Utils
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -61,7 +62,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
/**
* Binds a keyguard bottom area view to its view-model.
@@ -77,6 +77,7 @@ object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
private const val SCALE_SELECTED_BUTTON = 1.23f
private const val DIM_ALPHA = 0.3f
+ private const val TAG = "KeyguardBottomAreaViewBinder"
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -171,7 +172,7 @@ object KeyguardBottomAreaViewBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch {
+ launch("$TAG#viewModel.startButton") {
viewModel.startButton.collect { buttonModel ->
updateButton(
view = startButton,
@@ -184,7 +185,7 @@ object KeyguardBottomAreaViewBinder {
}
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch {
+ launch("$TAG#viewModel.endButton") {
viewModel.endButton.collect { buttonModel ->
updateButton(
view = endButton,
@@ -196,7 +197,7 @@ object KeyguardBottomAreaViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.isOverlayContainerVisible") {
viewModel.isOverlayContainerVisible.collect { isVisible ->
overlayContainer.visibility =
if (isVisible) {
@@ -207,7 +208,7 @@ object KeyguardBottomAreaViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.alpha") {
viewModel.alpha.collect { alpha ->
ambientIndicationArea?.apply {
this.importantForAccessibility =
@@ -222,7 +223,7 @@ object KeyguardBottomAreaViewBinder {
}
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch {
+ launch("$TAG#updateButtonAlpha") {
updateButtonAlpha(
view = startButton,
viewModel = viewModel.startButton,
@@ -231,7 +232,7 @@ object KeyguardBottomAreaViewBinder {
}
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch {
+ launch("$TAG#updateButtonAlpha") {
updateButtonAlpha(
view = endButton,
viewModel = viewModel.endButton,
@@ -239,13 +240,13 @@ object KeyguardBottomAreaViewBinder {
)
}
- launch {
+ launch("$TAG#viewModel.indicationAreaTranslationX") {
viewModel.indicationAreaTranslationX.collect { translationX ->
ambientIndicationArea?.translationX = translationX
}
}
- launch {
+ launch("$TAG#viewModel.indicationAreaTranslationY") {
configurationBasedDimensions
.map { it.defaultBurnInPreventionYOffsetPx }
.flatMapLatest { defaultBurnInOffsetY ->
@@ -257,7 +258,7 @@ object KeyguardBottomAreaViewBinder {
}
// If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch {
+ launch("$TAG#startButton.updateLayoutParams<ViewGroup") {
configurationBasedDimensions.collect { dimensions ->
startButton.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
@@ -270,7 +271,7 @@ object KeyguardBottomAreaViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.settingsMenuViewModel") {
viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect {
isVisible ->
settingsMenu.animateVisibility(visible = isVisible)
@@ -297,7 +298,7 @@ object KeyguardBottomAreaViewBinder {
// shows up in the Wallpaper Picker app. If we do that, then the
// settings menu should never be visible.
if (activityStarter != null) {
- launch {
+ launch("$TAG#viewModel.settingsMenuViewModel") {
viewModel.settingsMenuViewModel.shouldOpenSettings
.filter { it }
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 6255f0d44609..1b06a69213b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.keyguard.MigrateClocksToBlueprint
@@ -37,10 +38,10 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.DisposableHandle
object KeyguardClockViewBinder {
- private val TAG = KeyguardClockViewBinder::class.simpleName!!
+ private const val TAG = "KeyguardClockViewBinder"
// When changing to new clock, we need to remove old clock views from burnInLayer
private var lastClock: ClockController? = null
@JvmStatic
@@ -50,15 +51,12 @@ object KeyguardClockViewBinder {
viewModel: KeyguardClockViewModel,
keyguardClockInteractor: KeyguardClockInteractor,
blueprintInteractor: KeyguardBlueprintInteractor,
- ) {
- keyguardRootView.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
- }
- }
- keyguardRootView.repeatWhenAttached {
+ ): DisposableHandle {
+ keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
+
+ return keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#viewModel.currentClock") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.currentClock.collect { currentClock ->
cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
@@ -67,14 +65,14 @@ object KeyguardClockViewBinder {
applyConstraints(clockSection, keyguardRootView, true)
}
}
- launch {
+ launch("$TAG#viewModel.clockSize") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockSize.collect {
updateBurnInLayer(keyguardRootView, viewModel)
blueprintInteractor.refreshBlueprint(Type.ClockSize)
}
}
- launch {
+ launch("$TAG#viewModel.clockShouldBeCentered") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
viewModel.currentClock.value?.let {
@@ -91,7 +89,7 @@ object KeyguardClockViewBinder {
}
}
}
- launch {
+ launch("$TAG#viewModel.isAodIconsVisible") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
viewModel.currentClock.value?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 267d68e5e5e1..23c2491813f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -22,6 +22,7 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
@@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
/**
* Binds a keyguard indication area view to its view-model.
@@ -66,7 +66,7 @@ object KeyguardIndicationAreaBinder {
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.alpha") {
// Do not independently apply alpha, as [KeyguardRootViewModel] should work
// for this and all its children
if (
@@ -77,13 +77,13 @@ object KeyguardIndicationAreaBinder {
}
}
- launch {
+ launch("$TAG#viewModel.indicationAreaTranslationX") {
viewModel.indicationAreaTranslationX.collect { translationX ->
view.translationX = translationX
}
}
- launch {
+ launch("$TAG#viewModel.isIndicationAreaPadded") {
combine(
viewModel.isIndicationAreaPadded,
configurationBasedDimensions.map { it.indicationAreaPaddingPx },
@@ -97,7 +97,7 @@ object KeyguardIndicationAreaBinder {
.collect { paddingPx -> view.setPadding(paddingPx, 0, paddingPx, 0) }
}
- launch {
+ launch("$TAG#viewModel.indicationAreaTranslationY") {
configurationBasedDimensions
.map { it.defaultBurnInPreventionYOffsetPx }
.flatMapLatest { defaultBurnInOffsetY ->
@@ -106,7 +106,7 @@ object KeyguardIndicationAreaBinder {
.collect { translationY -> view.translationY = translationY }
}
- launch {
+ launch("$TAG#indicationText.setTextSize") {
configurationBasedDimensions.collect { dimensions ->
indicationText.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
@@ -119,7 +119,7 @@ object KeyguardIndicationAreaBinder {
}
}
- launch {
+ launch("$TAG#viewModel.configurationChange") {
viewModel.configurationChange.collect {
configurationBasedDimensions.value = loadFromResources(view)
}
@@ -147,4 +147,6 @@ object KeyguardIndicationAreaBinder {
val indicationAreaPaddingPx: Int,
val indicationTextSizePx: Int,
)
+
+ private const val TAG = "KeyguardIndicationAreaBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 9cc503c07955..09fe067f7724 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -20,11 +20,11 @@ package com.android.systemui.keyguard.ui.binder
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
-import kotlinx.coroutines.launch
object KeyguardLongPressViewBinder {
/**
@@ -64,7 +64,7 @@ object KeyguardLongPressViewBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.isLongPressHandlingEnabled") {
viewModel.isLongPressHandlingEnabled.collect { isEnabled ->
view.setLongPressHandlingEnabled(isEnabled)
}
@@ -72,4 +72,6 @@ object KeyguardLongPressViewBinder {
}
}
}
+
+ private const val TAG = "KeyguardLongPressViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 5906cfdda57e..486320af45a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -33,6 +33,8 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
@@ -44,7 +46,6 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.util.Utils
import kotlin.reflect.KSuspendFunction1
-import kotlinx.coroutines.launch
/** Binder for the small clock view, large clock view. */
object KeyguardPreviewClockViewBinder {
@@ -56,13 +57,17 @@ object KeyguardPreviewClockViewBinder {
) {
largeClockHostView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
+ launch("$TAG#viewModel.isLargeClockVisible") {
+ viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
+ }
}
}
smallClockHostView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
+ launch("$TAG#viewModel.isSmallClockVisible") {
+ viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
+ }
}
}
}
@@ -76,7 +81,7 @@ object KeyguardPreviewClockViewBinder {
) {
rootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.previewClock") {
var lastClock: ClockController? = null
viewModel.previewClock.collect { currentClock ->
lastClock?.let { clock ->
@@ -112,7 +117,7 @@ object KeyguardPreviewClockViewBinder {
constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
val largeClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
customizationR.dimen.small_clock_padding_top
) +
@@ -207,4 +212,5 @@ object KeyguardPreviewClockViewBinder {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+ private const val TAG = "KeyguardPreviewClockViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
index f5e4c6adcb91..49ae35a43c23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt
@@ -17,27 +17,47 @@
package com.android.systemui.keyguard.ui.binder
+import android.content.Context
import android.view.View
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
/** Binder for the small clock view, large clock view and smartspace. */
object KeyguardPreviewSmartspaceViewBinder {
@JvmStatic
fun bind(
+ context: Context,
smartspace: View,
+ splitShadePreview: Boolean,
viewModel: KeyguardPreviewSmartspaceViewModel,
) {
smartspace.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch { viewModel.smartspaceTopPadding.collect { smartspace.setTopPadding(it) } }
-
- launch { viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it } }
+ launch("$TAG#viewModel.selectedClockSize") {
+ viewModel.selectedClockSize.collect {
+ val topPadding =
+ when (it) {
+ SettingsClockSize.DYNAMIC ->
+ viewModel.getLargeClockSmartspaceTopPadding(
+ splitShadePreview,
+ )
+ SettingsClockSize.SMALL ->
+ viewModel.getSmallClockSmartspaceTopPadding(
+ splitShadePreview,
+ )
+ }
+ smartspace.setTopPadding(topPadding)
+ }
+ }
+ launch("$TAG#viewModel.shouldHideSmartspace") {
+ viewModel.shouldHideSmartspace.collect { smartspace.isInvisible = it }
+ }
}
}
}
@@ -45,4 +65,6 @@ object KeyguardPreviewSmartspaceViewBinder {
private fun View.setTopPadding(padding: Int) {
setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
}
+
+ private const val TAG = "KeyguardPreviewSmartspaceViewBinder"
}
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 abd79ab793d5..6c21e6cdb3bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,6 +30,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -41,11 +42,11 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.doOnEnd
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
/** This is only for a SINGLE Quick affordance */
object KeyguardQuickAffordanceViewBinder {
@@ -53,6 +54,7 @@ object KeyguardQuickAffordanceViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
private const val SCALE_SELECTED_BUTTON = 1.23f
private const val DIM_ALPHA = 0.3f
+ private const val TAG = "KeyguardQuickAffordanceViewBinder"
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -74,14 +76,15 @@ object KeyguardQuickAffordanceViewBinder {
alpha: Flow<Float>,
falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
+ mainImmediateDispatcher: CoroutineDispatcher,
messageDisplayer: (Int) -> Unit,
): Binding {
val button = view as ImageView
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
- view.repeatWhenAttached {
+ view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.collect") {
viewModel.collect { buttonModel ->
updateButton(
view = button,
@@ -93,7 +96,7 @@ object KeyguardQuickAffordanceViewBinder {
}
}
- launch {
+ launch("$TAG#updateButtonAlpha") {
updateButtonAlpha(
view = button,
viewModel = viewModel,
@@ -101,7 +104,7 @@ object KeyguardQuickAffordanceViewBinder {
)
}
- launch {
+ launch("$TAG#configurationBasedDimensions") {
configurationBasedDimensions.collect { dimensions ->
button.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 33052befadfc..44fd58267250 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -33,19 +33,23 @@ import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
-import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
+import com.android.systemui.common.ui.view.onTouchListener
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -54,7 +58,6 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
@@ -64,12 +67,13 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.util.kotlin.DisposableHandles
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
-import javax.inject.Provider
import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
@@ -77,7 +81,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -92,23 +95,27 @@ object KeyguardRootViewBinder {
chipbarCoordinator: ChipbarCoordinator,
screenOffAnimationController: ScreenOffAnimationController,
shadeInteractor: ShadeInteractor,
- clockControllerProvider: Provider<ClockController>?,
+ clockInteractor: KeyguardClockInteractor,
interactionJankMonitor: InteractionJankMonitor?,
deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
vibratorHelper: VibratorHelper?,
falsingManager: FalsingManager?,
keyguardViewMediator: KeyguardViewMediator?,
+ mainImmediateDispatcher: CoroutineDispatcher,
): DisposableHandle {
- var onLayoutChangeListener: OnLayoutChange? = null
+ val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
if (KeyguardBottomAreaRefactor.isEnabled) {
- view.setOnTouchListener { _, event ->
- if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
- viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+ disposables +=
+ view.onTouchListener { _, event ->
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ viewModel.setRootViewLastTapPosition(
+ Point(event.x.toInt(), event.y.toInt())
+ )
+ }
+ false
}
- false
- }
}
val burnInParams = MutableStateFlow(BurnInParameters())
@@ -117,10 +124,10 @@ object KeyguardRootViewBinder {
alpha = { view.alpha },
)
- val disposableHandle =
- view.repeatWhenAttached {
+ disposables +=
+ view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") {
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
->
if (biometricMessage?.message != null) {
@@ -139,7 +146,7 @@ object KeyguardRootViewBinder {
if (
KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
) {
- launch {
+ launch("$TAG#viewModel.alpha") {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -151,21 +158,21 @@ object KeyguardRootViewBinder {
}
if (MigrateClocksToBlueprint.isEnabled) {
- launch {
+ launch("$TAG#viewModel.burnInLayerVisibility") {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
childViews[aodNotificationIconContainerId]?.visibility = visibility
}
}
- launch {
+ launch("$TAG#viewModel.burnInLayerAlpha") {
viewModel.burnInLayerAlpha.collect { alpha ->
childViews[statusViewId]?.alpha = alpha
childViews[aodNotificationIconContainerId]?.alpha = alpha
}
}
- launch {
+ launch("$TAG#viewModel.topClippingBounds") {
val clipBounds = Rect()
viewModel.topClippingBounds.collect { clipTop ->
if (clipTop == null) {
@@ -182,13 +189,13 @@ object KeyguardRootViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.lockscreenStateAlpha") {
viewModel.lockscreenStateAlpha(viewState).collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
}
- launch {
+ launch("$TAG#viewModel.translationY") {
// When translation happens in burnInLayer, it won't be weather clock
// large clock isn't added to burnInLayer due to its scale transition
// so we also need to add translation to it here
@@ -200,7 +207,7 @@ object KeyguardRootViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.translationX") {
viewModel.translationX.collect { state ->
val px = state.value ?: return@collect
when {
@@ -227,7 +234,7 @@ object KeyguardRootViewBinder {
}
}
- launch {
+ launch("$TAG#viewModel.scale") {
viewModel.scale.collect { scaleViewModel ->
if (scaleViewModel.scaleClockOnly) {
// For clocks except weather clock, we have scale transition
@@ -258,7 +265,7 @@ object KeyguardRootViewBinder {
}
if (NotificationIconContainerRefactor.isEnabled) {
- launch {
+ launch("$TAG#viewModel.isNotifIconContainerVisible") {
val iconsAppearTranslationPx =
configuration
.getDimensionPixelSize(R.dimen.shelf_appear_translation)
@@ -275,18 +282,15 @@ object KeyguardRootViewBinder {
}
interactionJankMonitor?.let { jankMonitor ->
- launch {
+ launch("$TAG#viewModel.goneToAodTransition") {
viewModel.goneToAodTransition.collect {
when (it.transitionState) {
TransitionState.STARTED -> {
- val clockId =
- clockControllerProvider?.get()?.config?.id
- ?: MISSING_CLOCK_ID
+ val clockId = clockInteractor.renderedClockId
val builder =
InteractionJankMonitor.Configuration.Builder
.withView(CUJ_SCREEN_OFF_SHOW_AOD, view)
.setTag(clockId)
-
jankMonitor.begin(builder)
}
TransitionState.CANCELED ->
@@ -304,7 +308,7 @@ object KeyguardRootViewBinder {
}
}
- launch {
+ launch("$TAG#shadeInteractor.isAnyFullyExpanded") {
shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
view.visibility =
if (isFullyAnyExpanded) {
@@ -315,10 +319,12 @@ object KeyguardRootViewBinder {
}
}
- launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
+ launch("$TAG#burnInParams.collect") {
+ burnInParams.collect { viewModel.updateBurnInParams(it) }
+ }
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
- launch {
+ launch("$TAG#deviceEntryHapticsInteractor.playSuccessHaptic") {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
vibratorHelper.performHapticFeedback(
view,
@@ -328,7 +334,7 @@ object KeyguardRootViewBinder {
}
}
- launch {
+ launch("$TAG#deviceEntryHapticsInteractor.playErrorHaptic") {
deviceEntryHapticsInteractor.playErrorHaptic.collect {
vibratorHelper.performHapticFeedback(
view,
@@ -341,20 +347,13 @@ object KeyguardRootViewBinder {
}
}
- if (!MigrateClocksToBlueprint.isEnabled) {
- burnInParams.update { current ->
- current.copy(clockControllerProvider = clockControllerProvider)
- }
- }
-
if (MigrateClocksToBlueprint.isEnabled) {
burnInParams.update { current ->
current.copy(translationY = { childViews[burnInLayerId]?.translationY })
}
}
- onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
- view.addOnLayoutChangeListener(onLayoutChangeListener)
+ disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
// Views will be added or removed after the call to bind(). This is needed to avoid many
// calls to findViewById
@@ -369,24 +368,21 @@ object KeyguardRootViewBinder {
}
}
)
-
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
- }
- insets
+ disposables += DisposableHandle {
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
- return object : DisposableHandle {
- override fun dispose() {
- disposableHandle.dispose()
- view.removeOnLayoutChangeListener(onLayoutChangeListener)
- view.setOnHierarchyChangeListener(null)
- view.setOnApplyWindowInsetsListener(null)
- childViews.clear()
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- }
+
+ return disposables
}
/**
@@ -593,4 +589,5 @@ object KeyguardRootViewBinder {
private const val ID = "occluding_app_device_entry_unlock_msg"
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+ private const val TAG = "KeyguardRootViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index b1adef46e782..fa5756522a6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -22,6 +22,7 @@ import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
@@ -38,7 +39,6 @@ import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.launch
object KeyguardSettingsViewBinder {
fun bind(
@@ -52,7 +52,7 @@ object KeyguardSettingsViewBinder {
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel.isVisible") {
viewModel.isVisible.distinctUntilChanged().collect { isVisible ->
view.animateVisibility(visible = isVisible)
if (isVisible) {
@@ -78,7 +78,7 @@ object KeyguardSettingsViewBinder {
// shows up in the Wallpaper Picker app. If we do that, then the
// settings menu should never be visible.
if (activityStarter != null) {
- launch {
+ launch("$TAG#viewModel.shouldOpenSettings") {
viewModel.shouldOpenSettings
.filter { it }
.collect {
@@ -91,7 +91,7 @@ object KeyguardSettingsViewBinder {
}
}
- launch {
+ launch("$TAG#rootViewModel?.lastRootViewTapPosition") {
rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
if (view.isVisible) {
val hitRect = Rect()
@@ -136,4 +136,6 @@ object KeyguardSettingsViewBinder {
}
.start()
}
+
+ private const val TAG = "KeyguardSettingsViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 9aebf66aa067..6b11dc57e096 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -21,6 +21,7 @@ import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
@@ -30,7 +31,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
-import kotlinx.coroutines.launch
object KeyguardSmartspaceViewBinder {
@JvmStatic
@@ -42,7 +42,7 @@ object KeyguardSmartspaceViewBinder {
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
->
@@ -61,7 +61,7 @@ object KeyguardSmartspaceViewBinder {
}
}
- launch {
+ launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
smartspaceViewModel.bcSmartspaceVisibility.collect {
updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
@@ -148,4 +148,6 @@ object KeyguardSmartspaceViewBinder {
}
}
}
+
+ private const val TAG = "KeyguardSmartspaceViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
index 599f69f4bc38..fd27dc39299b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.ui.binder
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/**
* Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
@@ -32,6 +32,10 @@ object KeyguardSurfaceBehindViewBinder {
applier: KeyguardSurfaceBehindParamsApplier,
scope: CoroutineScope
) {
- scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+ scope.launch("$TAG#viewModel.surfaceBehindViewParams") {
+ viewModel.surfaceBehindViewParams.collect { applier.viewParams = it }
+ }
}
+
+ private const val TAG = "KeyguardSurfaceBehindViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
index f1da8826a22c..b2ee68967878 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.binder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.LightRevealScrim
@@ -28,11 +29,11 @@ object LightRevealScrimViewBinder {
fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) {
revealScrim.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
+ launch("$TAG#viewModel.revealAmount") {
viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount }
}
- launch {
+ launch("$TAG#viewModel.lightRevealEffect") {
viewModel.lightRevealEffect.collect { effect ->
revealScrim.revealEffect = effect
}
@@ -40,4 +41,6 @@ object LightRevealScrimViewBinder {
}
}
}
+
+ private const val TAG = "LightRevealScrimViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
index fc0c78a6f76b..ae46dd3a6ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -16,39 +16,41 @@
package com.android.systemui.keyguard.ui.binder
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/**
* Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
* surface behind the keyguard.
*/
object WindowManagerLockscreenVisibilityViewBinder {
+ private const val TAG = "WindowManagerLockscreenVisibilityViewBinder"
+
@JvmStatic
fun bind(
viewModel: WindowManagerLockscreenVisibilityViewModel,
lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
scope: CoroutineScope
) {
- scope.launch {
+ scope.launch("$TAG#viewModel.surfaceBehindVisibility") {
viewModel.surfaceBehindVisibility.collect {
lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
}
}
- scope.launch {
+ scope.launch("$TAG#viewModel.lockscreenVisibility") {
viewModel.lockscreenVisibility.collect {
lockscreenVisibilityManager.setLockscreenShown(it)
}
}
- scope.launch {
+ scope.launch("$TAG#viewModel.aodVisibility") {
viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
}
- scope.launch {
+ scope.launch("$TAG#viewModel.surfaceBehindAnimating") {
viewModel.surfaceBehindAnimating.collect {
lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
}
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 cbf52efed22f..42bd4aff1dc4 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
@@ -46,8 +46,8 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import 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
@@ -61,6 +61,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -141,6 +142,7 @@ constructor(
private val secureSettings: SecureSettings,
private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
private val defaultShortcutsSection: DefaultShortcutsSection,
+ private val keyguardClockInteractor: KeyguardClockInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -325,13 +327,9 @@ constructor(
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
- KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
- previewContext.resources,
- )
- val startPadding: Int =
- previewContext.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
- val endPadding: Int =
- previewContext.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+ smartspaceViewModel.getLargeClockSmartspaceTopPadding(previewInSplitShade())
+ val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding()
+ val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding()
smartSpaceView?.let {
it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
@@ -369,6 +367,7 @@ constructor(
),
)
}
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
val keyguardRootView = KeyguardRootView(previewContext, null)
@@ -382,12 +381,13 @@ constructor(
chipbarCoordinator,
screenOffAnimationController,
shadeInteractor,
- null, // clock provider only needed for burn in
+ keyguardClockInteractor,
null, // jank monitor not required for preview mode
null, // device entry haptics not required preview mode
null, // device entry haptics not required for preview mode
null, // falsing manager not required for preview mode
null, // keyguard view mediator is not required for preview mode
+ mainDispatcher,
)
}
rootView.addView(
@@ -426,7 +426,15 @@ constructor(
}
setUpSmartspace(previewContext, rootView)
- smartSpaceView?.let { KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) }
+
+ smartSpaceView?.let {
+ KeyguardPreviewSmartspaceViewBinder.bind(
+ context,
+ it,
+ previewInSplitShade(),
+ smartspaceViewModel
+ )
+ }
setupCommunalTutorialIndicator(keyguardRootView)
}
@@ -446,6 +454,7 @@ constructor(
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ mainImmediateDispatcher = mainDispatcher,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -460,6 +469,7 @@ constructor(
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ mainImmediateDispatcher = mainDispatcher,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -530,7 +540,7 @@ constructor(
)
)
layoutParams.topMargin =
- KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+ SystemBarUtils.getStatusBarHeight(previewContext) +
resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_padding_top
)
@@ -705,6 +715,14 @@ constructor(
smallClockHostView.addView(clock.smallClock.view)
}
+ /*
+ * When multi_crop_preview_ui_flag is on, we can preview portrait in split shadow direction
+ * or vice versa. So we need to decide preview direction by width and height
+ */
+ private fun previewInSplitShade(): Boolean {
+ return width > height
+ }
+
companion object {
private const val TAG = "KeyguardPreviewRenderer"
private const val OVERLAY_CATEGORY_THEME_STYLE = "android.theme.customization.theme_style"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 2e9663897f89..5404729d1819 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -36,6 +36,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
class AlignShortcutsToUdfpsSection
@Inject
@@ -47,6 +48,7 @@ constructor(
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -64,6 +66,7 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
@@ -74,6 +77,7 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 4a09939d0aba..e0bf8152d11f 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
@@ -43,10 +43,9 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.util.Utils
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
internal fun ConstraintSet.setVisibility(
views: Iterable<View>,
@@ -64,23 +63,26 @@ constructor(
private val clockInteractor: KeyguardClockInteractor,
protected val keyguardClockViewModel: KeyguardClockViewModel,
private val context: Context,
- private val splitShadeStateController: SplitShadeStateController,
val smartspaceViewModel: KeyguardSmartspaceViewModel,
val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
+ private var handle: DisposableHandle? = null
+
override fun addViews(constraintLayout: ConstraintLayout) {}
override fun bindData(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
}
- KeyguardClockViewBinder.bind(
- this,
- constraintLayout,
- keyguardClockViewModel,
- clockInteractor,
- blueprintInteractor.get()
- )
+ handle?.dispose()
+ handle =
+ KeyguardClockViewBinder.bind(
+ this,
+ constraintLayout,
+ keyguardClockViewModel,
+ clockInteractor,
+ blueprintInteractor.get()
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -92,7 +94,13 @@ constructor(
}
}
- override fun removeViews(constraintLayout: ConstraintLayout) {}
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ if (!MigrateClocksToBlueprint.isEnabled) {
+ return
+ }
+ handle?.dispose()
+ handle = null
+ }
private fun buildConstraints(
clock: ClockController,
@@ -162,12 +170,7 @@ constructor(
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(R.id.lockscreen_clock_view_large, END, guideline, END)
connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
- var largeClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
- context.resources.getDimensionPixelSize(
- customizationR.dimen.small_clock_padding_top
- ) +
- context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ var largeClockTopMargin = KeyguardClockViewModel.getLargeClockTopMargin(context)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
@@ -187,14 +190,7 @@ constructor(
context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
)
- val smallClockTopMargin =
- if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
- context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
- } else {
- context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- Utils.getStatusBarHeaderHeightKeyguard(context)
- }
-
+ val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin(context)
create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
setGuidelineBegin(R.id.small_clock_guideline_top, smallClockTopMargin)
connect(R.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 29041d1665c3..865e989c5b68 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -30,6 +30,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -47,6 +48,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -67,6 +69,7 @@ constructor(
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
private val vibratorHelper: Lazy<VibratorHelper>,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
@@ -104,6 +107,7 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
+ mainImmediateDispatcher,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 45b82576c6c4..27ca5cdbad17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -35,6 +35,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
class DefaultShortcutsSection
@Inject
@@ -46,6 +47,7 @@ constructor(
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -63,6 +65,7 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
@@ -73,6 +76,7 @@ constructor(
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ mainImmediateDispatcher,
) {
indicationController.showTransientIndication(it)
}
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 1847d2794787..eaa5e3367f49 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
@@ -96,12 +96,8 @@ constructor(
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
- val horizontalPaddingStart =
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
- val horizontalPaddingEnd =
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ val horizontalPaddingStart = KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
+ val horizontalPaddingEnd = KeyguardSmartspaceViewModel.getSmartspaceEndMargin(context)
constraintSet.apply {
// migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index ded680c8baa3..df0b3dc3cbce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -60,12 +60,12 @@ constructor(
val iconLocation: Flow<IconLocation> =
isSupported.flatMapLatest { supportsUI ->
if (supportsUI) {
- fingerprintPropertyInteractor.sensorLocation.map { sensorLocation ->
+ fingerprintPropertyInteractor.udfpsSensorBounds.map { bounds ->
IconLocation(
- left = (sensorLocation.centerX - sensorLocation.radius).toInt(),
- top = (sensorLocation.centerY - sensorLocation.radius).toInt(),
- right = (sensorLocation.centerX + sensorLocation.radius).toInt(),
- bottom = (sensorLocation.centerY + sensorLocation.radius).toInt(),
+ left = bounds.left,
+ top = bounds.top,
+ right = bounds.right,
+ bottom = bounds.bottom,
)
}
} else {
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 20549328838f..4ddd57110b38 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
@@ -33,10 +33,8 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.StateToValue
-import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import javax.inject.Inject
-import javax.inject.Provider
import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -128,12 +126,12 @@ constructor(
yDimenResourceId = R.dimen.burn_in_prevention_offset_y
),
) { interpolated, burnIn ->
+ val useAltAod =
+ keyguardClockViewModel.currentClock.value?.let { clock ->
+ clock.config.useAlternateSmartspaceAODTransition
+ } == true
val useScaleOnly =
- (clockController(params.clockControllerProvider)
- ?.get()
- ?.config
- ?.useAlternateSmartspaceAODTransition
- ?: false) && keyguardClockViewModel.clockSize.value == KeyguardClockSwitch.LARGE
+ useAltAod && keyguardClockViewModel.clockSize.value == KeyguardClockSwitch.LARGE
if (useScaleOnly) {
BurnInModel(
@@ -164,21 +162,10 @@ constructor(
}
}
}
-
- private fun clockController(
- provider: Provider<ClockController>?,
- ): Provider<ClockController>? {
- return if (MigrateClocksToBlueprint.isEnabled) {
- Provider { keyguardClockViewModel.currentClock.value }
- } else {
- provider
- }
- }
}
/** UI-sourced parameters to pass into the various methods of [AodBurnInViewModel]. */
data class BurnInParameters(
- val clockControllerProvider: Provider<ClockController>? = null,
/** System insets that keyguard needs to stay out of */
val topInset: Int = 0,
/** The min y-value of the visible elements on lockscreen */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 3d649512f342..c9251c7c5473 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -17,9 +17,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
+import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
+import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
+import com.android.systemui.customization.R as customizationR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -47,7 +50,7 @@ constructor(
private val keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
notifsKeyguardInteractor: NotificationsKeyguardInteractor,
- private val shadeInteractor: ShadeInteractor,
+ @VisibleForTesting val shadeInteractor: ShadeInteractor,
) {
var burnInLayer: Layer? = null
val useLargeClock: Boolean
@@ -161,6 +164,16 @@ constructor(
return topMargin
}
+ companion object {
+ fun getLargeClockTopMargin(context: Context): Int {
+ return SystemBarUtils.getStatusBarHeight(context) +
+ context.resources.getDimensionPixelSize(
+ customizationR.dimen.small_clock_padding_top
+ ) +
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ }
+ }
+
enum class ClockLayout {
LARGE_CLOCK,
SMALL_CLOCK,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 33718c410905..b57e3ecbe05b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -17,13 +17,13 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import android.content.res.Resources
-import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -33,15 +33,11 @@ class KeyguardPreviewSmartspaceViewModel
constructor(
@Application private val context: Context,
interactor: KeyguardClockInteractor,
+ val smartspaceViewModel: KeyguardSmartspaceViewModel,
+ val clockViewModel: KeyguardClockViewModel,
) {
- val smartspaceTopPadding: Flow<Int> =
- interactor.selectedClockSize.map {
- when (it) {
- SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
- SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources)
- }
- }
+ val selectedClockSize: StateFlow<SettingsClockSize> = interactor.selectedClockSize
val shouldHideSmartspace: Flow<Boolean> =
combine(
@@ -59,34 +55,37 @@ constructor(
}
}
- companion object {
- fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
- return with(resources) {
- getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
- getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
- getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
- }
- }
+ fun getSmartspaceStartPadding(): Int {
+ return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
+ }
- fun getSmallClockSmartspaceTopPadding(resources: Resources): Int {
- return with(resources) {
- getStatusBarHeight(this) +
- getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
- ) +
- getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
- }
- }
+ fun getSmartspaceEndPadding(): Int {
+ return KeyguardSmartspaceViewModel.getSmartspaceEndMargin(context)
+ }
+
+ fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean): Int {
+ return getSmallClockTopPadding(splitShadePreview) +
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.small_clock_height
+ )
+ }
+
+ fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean): Int {
+ return getSmallClockTopPadding(splitShadePreview)
+ }
- fun getStatusBarHeight(resource: Resources): Int {
- var result = 0
- val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
- if (resourceId > 0) {
- result = resource.getDimensionPixelSize(resourceId)
+ /*
+ * SmallClockTopPadding decides the top position of smartspace
+ */
+ private fun getSmallClockTopPadding(splitShadePreview: Boolean): Int {
+ return with(context.resources) {
+ if (splitShadePreview) {
+ getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+ } else {
+ getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+ getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+ getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
}
- return result
}
}
}
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 e8313a9f739c..64e15659d08b 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
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Point
import android.util.MathUtils
import android.view.View.VISIBLE
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -268,7 +269,9 @@ constructor(
burnInJob?.cancel()
burnInJob =
- scope.launch { aodBurnInViewModel.movement(params).collect { burnInModel.value = it } }
+ scope.launch("$TAG#aodBurnInViewModel") {
+ aodBurnInViewModel.movement(params).collect { burnInModel.value = it }
+ }
}
val scale: Flow<BurnInScaleViewModel> =
@@ -368,4 +371,8 @@ constructor(
fun setRootViewLastTapPosition(point: Point) {
keyguardInteractor.setLastRootViewTapPosition(point)
}
+
+ companion object {
+ private const val TAG = "KeyguardRootViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index e8c1ab5feccc..9e7dbd410471 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
+import com.android.systemui.res.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -83,4 +85,16 @@ constructor(
/* trigger clock and smartspace constraints change when smartspace appears */
var bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
+
+ companion object {
+ fun getSmartspaceStartMargin(context: Context): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ }
+
+ fun getSmartspaceEndMargin(context: Context): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 3a19780c7017..a09d58ac381b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -21,15 +21,21 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -43,6 +49,8 @@ constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
+ keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : DeviceEntryIconTransition {
private val transitionAnimation =
@@ -74,11 +82,28 @@ constructor(
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- startTime = 233.milliseconds,
- duration = 250.milliseconds,
- onStep = { it },
- name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
+ merge(
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
+ ),
+ // Required to fix a bug where the shade expands while lockscreenAlpha=1f, due to a call
+ // to setOccluded(false) triggering a reset() call in KeyguardViewMediator. The
+ // permanent solution is to only expand the shade once the keyguard transition from
+ // OCCLUDED starts, but that requires more refactoring of expansion amounts. For now,
+ // emit alpha = 0f for OCCLUDED -> LOCKSCREEN whenever isOccluded flips from true to
+ // false while currentState == OCCLUDED, so that alpha = 0f when that expansion occurs.
+ // TODO(b/332946323): Remove this once it's no longer needed.
+ keyguardInteractor.isKeyguardOccluded
+ .pairwise()
+ .filter { (wasOccluded, isOccluded) ->
+ wasOccluded &&
+ !isOccluded &&
+ keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+ }
+ .map { 0f }
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 1c11178b5b35..5dafd94f05e9 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -26,7 +26,9 @@ import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.createCoroutineTracingContext
import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.coroutineTracing
import com.android.systemui.util.Assert
+import com.android.systemui.util.Compile
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.Dispatchers
@@ -69,6 +71,12 @@ fun View.repeatWhenAttached(
// presumably want to call view methods that require being called from said UI thread.
val lifecycleCoroutineContext =
Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
+ val traceName =
+ if (Compile.IS_DEBUG && coroutineTracing()) {
+ traceSectionName()
+ } else {
+ DEFAULT_TRACE_NAME
+ }
var lifecycleOwner: ViewLifecycleOwner? = null
val onAttachListener =
object : View.OnAttachStateChangeListener {
@@ -77,6 +85,7 @@ fun View.repeatWhenAttached(
lifecycleOwner?.onDestroy()
lifecycleOwner =
createLifecycleOwnerAndRun(
+ traceName,
view,
lifecycleCoroutineContext,
block,
@@ -93,6 +102,7 @@ fun View.repeatWhenAttached(
if (view.isAttachedToWindow) {
lifecycleOwner =
createLifecycleOwnerAndRun(
+ traceName,
view,
lifecycleCoroutineContext,
block,
@@ -109,18 +119,14 @@ fun View.repeatWhenAttached(
}
private fun createLifecycleOwnerAndRun(
+ nameForTrace: String,
view: View,
coroutineContext: CoroutineContext,
block: suspend LifecycleOwner.(View) -> Unit,
): ViewLifecycleOwner {
return ViewLifecycleOwner(view).apply {
onCreate()
- lifecycleScope.launch(
- "ViewLifecycleOwner(${view::class.java.simpleName})",
- coroutineContext
- ) {
- block(view)
- }
+ lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
}
}
@@ -186,3 +192,24 @@ class ViewLifecycleOwner(
}
}
}
+
+private fun isFrameInteresting(frame: StackWalker.StackFrame): Boolean =
+ frame.className != CURRENT_CLASS_NAME && frame.className != JAVA_ADAPTER_CLASS_NAME
+
+/** Get a name for the trace section include the name of the call site. */
+private fun traceSectionName(): String {
+ val interestingFrame =
+ StackWalker.getInstance().walk { stream ->
+ stream.filter(::isFrameInteresting).limit(5).findFirst()
+ }
+ if (interestingFrame.isPresent) {
+ val frame = interestingFrame.get()
+ return "${frame.className}#${frame.methodName}:${frame.lineNumber} [$DEFAULT_TRACE_NAME]"
+ } else {
+ return DEFAULT_TRACE_NAME
+ }
+}
+
+private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
+private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
+private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
index 40a132a491a1..d57b04938d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
@@ -17,6 +17,13 @@
package com.android.systemui.media.controls.domain.pipeline.interactor
import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.Expandable
+import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
@@ -24,6 +31,8 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaRecModel
import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.plugins.ActivityStarter
+import java.net.URISyntaxException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -42,6 +51,8 @@ constructor(
@Application private val applicationContext: Context,
repository: MediaFilterRepository,
private val mediaDataProcessor: MediaDataProcessor,
+ private val broadcastSender: BroadcastSender,
+ private val activityStarter: ActivityStarter,
) {
val recommendations: Flow<MediaRecommendationsModel> =
@@ -54,8 +65,53 @@ constructor(
.distinctUntilChanged()
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
- fun removeMediaRecommendations(key: String, delayMs: Long) {
+ fun removeMediaRecommendations(key: String, dismissIntent: Intent?, delayMs: Long) {
mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: extras missing dismiss_intent.")
+ return
+ }
+
+ val className = dismissIntent.component?.className
+ if (className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ applicationContext.startActivity(dismissIntent)
+ } else {
+ broadcastSender.sendBroadcast(dismissIntent)
+ }
+ }
+
+ fun startSettings() {
+ activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true)
+ }
+
+ fun startClickIntent(expandable: Expandable, intent: Intent) {
+ if (shouldActivityOpenInForeground(intent)) {
+ // Request to unlock the device if the activity needs to be opened in foreground.
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ expandable.activityTransitionController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
+ )
+ )
+ } else {
+ // Otherwise, open the activity in background directly.
+ applicationContext.startActivity(intent)
+ }
+ }
+
+ /** Returns if the action will open the activity in foreground. */
+ private fun shouldActivityOpenInForeground(intent: Intent): Boolean {
+ val intentString = intent.extras?.getString(EXTRAS_SMARTSPACE_INTENT) ?: return false
+ try {
+ val wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME)
+ return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false)
+ } catch (e: URISyntaxException) {
+ Log.wtf(TAG, "Failed to create intent from URI: $intentString")
+ e.printStackTrace()
+ }
+ return false
}
private fun toRecommendationsModel(data: SmartspaceMediaData): MediaRecommendationsModel {
@@ -76,4 +132,21 @@ constructor(
)
}
}
+
+ companion object {
+
+ private const val TAG = "MediaRecommendationsInteractor"
+
+ // TODO (b/237284176) : move AGSA reference out.
+ private const val EXTRAS_SMARTSPACE_INTENT =
+ "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"
+ @VisibleForTesting
+ const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME =
+ "com.google.android.apps.gsa.staticplugins.opa.smartspace." +
+ "ExportedSmartspaceTrampolineActivity"
+
+ private const val KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"
+
+ private val SETTINGS_INTENT = Intent(Settings.ACTION_MEDIA_CONTROLS_SETTINGS)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.kt
new file mode 100644
index 000000000000..eec43a68adfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaArtworkHelper.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.media.controls.ui.util
+
+import android.app.WallpaperColors
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.LayerDrawable
+import android.util.Log
+import com.android.systemui.media.controls.ui.animation.backgroundEndFromScheme
+import com.android.systemui.media.controls.ui.animation.backgroundStartFromScheme
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.util.getColorWithAlpha
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+object MediaArtworkHelper {
+
+ /**
+ * This method should be called from a background thread. WallpaperColors.fromBitmap takes a
+ * good amount of time. We do that work on the background executor to avoid stalling animations
+ * on the UI Thread.
+ */
+ suspend fun getWallpaperColor(
+ applicationContext: Context,
+ backgroundDispatcher: CoroutineDispatcher,
+ artworkIcon: Icon?,
+ tag: String,
+ ): WallpaperColors? =
+ withContext(backgroundDispatcher) {
+ return@withContext artworkIcon?.let {
+ if (it.type == Icon.TYPE_BITMAP || it.type == Icon.TYPE_ADAPTIVE_BITMAP) {
+ // Avoids extra processing if this is already a valid bitmap
+ it.bitmap.let { artworkBitmap ->
+ if (artworkBitmap.isRecycled) {
+ Log.d(tag, "Cannot load wallpaper color from a recycled bitmap")
+ null
+ } else {
+ WallpaperColors.fromBitmap(artworkBitmap)
+ }
+ }
+ } else {
+ it.loadDrawable(applicationContext)?.let { artworkDrawable ->
+ WallpaperColors.fromDrawable(artworkDrawable)
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a scaled [Drawable] of a given [Icon] centered in [width]x[height] background size.
+ */
+ fun getScaledBackground(context: Context, icon: Icon, width: Int, height: Int): Drawable? {
+ val drawable = icon.loadDrawable(context)
+ val bounds = Rect(0, 0, width, height)
+ if (bounds.width() > width || bounds.height() > height) {
+ val offsetX = (bounds.width() - width) / 2.0f
+ val offsetY = (bounds.height() - height) / 2.0f
+ bounds.offset(-offsetX.toInt(), -offsetY.toInt())
+ }
+ drawable?.bounds = bounds
+ return drawable
+ }
+
+ /** Adds [gradient] on a given [albumArt] drawable using [colorScheme]. */
+ fun setUpGradientColorOnDrawable(
+ albumArt: Drawable?,
+ gradient: GradientDrawable,
+ colorScheme: ColorScheme,
+ startAlpha: Float,
+ endAlpha: Float
+ ): LayerDrawable {
+ gradient.colors =
+ intArrayOf(
+ getColorWithAlpha(backgroundStartFromScheme(colorScheme), startAlpha),
+ getColorWithAlpha(backgroundEndFromScheme(colorScheme), endAlpha)
+ )
+ return LayerDrawable(arrayOf(albumArt, gradient))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
new file mode 100644
index 000000000000..e508e1bb1b67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+
+/** Models UI state for media guts menu */
+data class GutsViewModel(
+ val gutsText: CharSequence,
+ @ColorInt val textColor: Int,
+ @ColorInt val buttonBackgroundColor: Int,
+ @ColorInt val buttonTextColor: Int,
+ val isDismissEnabled: Boolean = true,
+ val onDismissClicked: () -> Unit,
+ val cancelTextBackground: Drawable?,
+ val onSettingsClicked: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
new file mode 100644
index 000000000000..2f9fc9bc699a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+
+/** Models UI state for media recommendation item */
+data class MediaRecViewModel(
+ val contentDescription: CharSequence,
+ val title: CharSequence = "",
+ @ColorInt val titleColor: Int,
+ val subtitle: CharSequence = "",
+ @ColorInt val subtitleColor: Int,
+ /** track progress [0 - 100] for the recommendation album. */
+ val progress: Int = 0,
+ @ColorInt val progressColor: Int,
+ val albumIcon: Drawable? = null,
+ val appIcon: Drawable? = null,
+ val onClicked: ((Expandable, Int) -> Unit),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
new file mode 100644
index 000000000000..19ea00d439b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.app.WallpaperColors
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.LayerDrawable
+import android.os.Process
+import android.util.Log
+import androidx.appcompat.content.res.AppCompatResources
+import com.android.internal.logging.InstanceId
+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.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
+import com.android.systemui.media.controls.shared.model.MediaRecModel
+import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
+import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.surfaceFromScheme
+import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme
+import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION
+import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Models UI state and handles user input for media recommendations */
+@SysUISingleton
+class MediaRecommendationsViewModel
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val interactor: MediaRecommendationsInteractor,
+ private val logger: MediaUiEventLogger,
+) {
+
+ val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
+ interactor.recommendations
+ .map { recsCard -> toRecsViewModel(recsCard) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ /**
+ * Called whenever the recommendation has been expired or removed by the user. This method
+ * removes the recommendation card entirely from the carousel.
+ */
+ private fun onMediaRecommendationsDismissed(
+ key: String,
+ uid: Int,
+ packageName: String,
+ dismissIntent: Intent?,
+ instanceId: InstanceId?
+ ) {
+ // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_DISMISS_EVENT).
+ logger.logLongPressDismiss(uid, packageName, instanceId)
+ interactor.removeMediaRecommendations(key, dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION)
+ }
+
+ private fun onClicked(
+ expandable: Expandable,
+ intent: Intent?,
+ packageName: String,
+ instanceId: InstanceId?,
+ index: Int
+ ) {
+ if (intent == null || intent.extras == null) {
+ Log.e(TAG, "No tap action can be set up")
+ return
+ }
+
+ if (index == -1) {
+ logger.logRecommendationCardTap(packageName, instanceId)
+ } else {
+ logger.logRecommendationItemTap(packageName, instanceId, index)
+ }
+ // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT).
+ interactor.startClickIntent(expandable, intent)
+ }
+
+ private suspend fun toRecsViewModel(model: MediaRecommendationsModel): MediaRecsCardViewModel? {
+ if (!model.areRecommendationsValid) {
+ Log.e(TAG, "Received an invalid recommendation list")
+ return null
+ }
+ if (model.appName == null || model.uid == Process.INVALID_UID) {
+ Log.w(TAG, "Fail to get media recommendation's app info")
+ return null
+ }
+
+ val scheme = getColorScheme(model.packageName) ?: return null
+
+ // Capture width & height from views in foreground for artwork scaling in background
+ val width =
+ applicationContext.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+ val height =
+ applicationContext.resources.getDimensionPixelSize(
+ R.dimen.qs_media_rec_album_height_expanded
+ )
+
+ val appIcon = applicationContext.packageManager.getApplicationIcon(model.packageName)
+ val textPrimaryColor = textPrimaryFromScheme(scheme)
+ val textSecondaryColor = textSecondaryFromScheme(scheme)
+ val backgroundColor = surfaceFromScheme(scheme)
+
+ var areTitlesVisible = false
+ var areSubtitlesVisible = false
+ val mediaRecs =
+ model.mediaRecs.map { mediaRecModel ->
+ areTitlesVisible = areTitlesVisible || !mediaRecModel.title.isNullOrEmpty()
+ areSubtitlesVisible = areSubtitlesVisible || !mediaRecModel.subtitle.isNullOrEmpty()
+ val progress = MediaDataUtils.getDescriptionProgress(mediaRecModel.extras) ?: 0.0
+ MediaRecViewModel(
+ contentDescription =
+ setUpMediaRecContentDescription(mediaRecModel, model.appName),
+ title = mediaRecModel.title ?: "",
+ titleColor = textPrimaryColor,
+ subtitle = mediaRecModel.subtitle ?: "",
+ subtitleColor = textSecondaryColor,
+ progress = (progress * 100).toInt(),
+ progressColor = textPrimaryColor,
+ albumIcon =
+ getRecCoverBackground(
+ mediaRecModel.icon,
+ width,
+ height,
+ ),
+ appIcon = appIcon,
+ onClicked = { expandable, index ->
+ onClicked(
+ expandable,
+ mediaRecModel.intent,
+ model.packageName,
+ model.instanceId,
+ index,
+ )
+ }
+ )
+ }
+ // Subtitles should only be visible if titles are visible.
+ areSubtitlesVisible = areTitlesVisible && areSubtitlesVisible
+
+ return MediaRecsCardViewModel(
+ contentDescription = { gutsVisible ->
+ if (gutsVisible) {
+ applicationContext.getString(
+ R.string.controls_media_close_session,
+ model.appName
+ )
+ } else {
+ applicationContext.getString(R.string.controls_media_smartspace_rec_header)
+ }
+ },
+ cardColor = backgroundColor,
+ cardTitleColor = textPrimaryColor,
+ onClicked = { expandable ->
+ onClicked(
+ expandable,
+ model.dismissIntent,
+ model.packageName,
+ model.instanceId,
+ index = -1
+ )
+ },
+ onLongClicked = {
+ logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
+ },
+ mediaRecs = mediaRecs,
+ areTitlesVisible = areTitlesVisible,
+ areSubtitlesVisible = areSubtitlesVisible,
+ gutsMenu = toGutsViewModel(model, scheme),
+ )
+ }
+
+ private fun toGutsViewModel(
+ model: MediaRecommendationsModel,
+ scheme: ColorScheme
+ ): GutsViewModel {
+ return GutsViewModel(
+ gutsText =
+ applicationContext.getString(R.string.controls_media_close_session, model.appName),
+ textColor = textPrimaryFromScheme(scheme),
+ buttonBackgroundColor = accentPrimaryFromScheme(scheme),
+ buttonTextColor = surfaceFromScheme(scheme),
+ onDismissClicked = {
+ onMediaRecommendationsDismissed(
+ model.key,
+ model.uid,
+ model.packageName,
+ model.dismissIntent,
+ model.instanceId
+ )
+ },
+ cancelTextBackground =
+ applicationContext.getDrawable(R.drawable.qs_media_outline_button),
+ onSettingsClicked = {
+ logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
+ interactor.startSettings()
+ },
+ )
+ }
+
+ /** Returns the recommendation album cover of [width]x[height] size. */
+ private suspend fun getRecCoverBackground(icon: Icon?, width: Int, height: Int): Drawable =
+ withContext(backgroundDispatcher) {
+ return@withContext MediaArtworkHelper.getWallpaperColor(
+ applicationContext,
+ backgroundDispatcher,
+ icon,
+ TAG,
+ )
+ ?.let { wallpaperColors ->
+ addGradientToRecommendationAlbum(
+ icon!!,
+ ColorScheme(wallpaperColors, true, Style.CONTENT),
+ width,
+ height
+ )
+ }
+ ?: ColorDrawable(Color.TRANSPARENT)
+ }
+
+ private fun addGradientToRecommendationAlbum(
+ artworkIcon: Icon,
+ mutableColorScheme: ColorScheme,
+ width: Int,
+ height: Int
+ ): LayerDrawable {
+ // First try scaling rec card using bitmap drawable.
+ // If returns null, set drawable bounds.
+ val albumArt =
+ getScaledRecommendationCover(artworkIcon, width, height)
+ ?: MediaArtworkHelper.getScaledBackground(
+ applicationContext,
+ artworkIcon,
+ width,
+ height
+ )
+ val gradient =
+ AppCompatResources.getDrawable(applicationContext, R.drawable.qs_media_rec_scrim)
+ ?.mutate() as GradientDrawable
+ return MediaArtworkHelper.setUpGradientColorOnDrawable(
+ albumArt,
+ gradient,
+ mutableColorScheme,
+ MEDIA_REC_SCRIM_START_ALPHA,
+ MEDIA_REC_SCRIM_END_ALPHA
+ )
+ }
+
+ private fun setUpMediaRecContentDescription(
+ mediaRec: MediaRecModel,
+ appName: CharSequence?
+ ): CharSequence {
+ // Set up the accessibility label for the media item.
+ val artistName = mediaRec.extras?.getString(KEY_SMARTSPACE_ARTIST_NAME, "")
+ return if (artistName.isNullOrEmpty()) {
+ applicationContext.getString(
+ R.string.controls_media_smartspace_rec_item_no_artist_description,
+ mediaRec.title,
+ appName
+ )
+ } else {
+ applicationContext.getString(
+ R.string.controls_media_smartspace_rec_item_description,
+ mediaRec.title,
+ artistName,
+ appName
+ )
+ }
+ }
+
+ private fun getColorScheme(packageName: String): ColorScheme? {
+ // Set up recommendation card's header.
+ return try {
+ val packageManager = applicationContext.packageManager
+ val applicationInfo = packageManager.getApplicationInfo(packageName, 0 /* flags */)
+ // Set up media source app's logo.
+ val icon = packageManager.getApplicationIcon(applicationInfo)
+ ColorScheme(WallpaperColors.fromDrawable(icon), darkTheme = true)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Fail to get media recommendation's app info", e)
+ null
+ }
+ }
+
+ /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */
+ private fun getScaledRecommendationCover(
+ artworkIcon: Icon,
+ width: Int,
+ height: Int
+ ): Drawable? {
+ check(width > 0) { "Width must be a positive number but was $width" }
+ check(height > 0) { "Height must be a positive number but was $height" }
+
+ return if (
+ artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP
+ ) {
+ artworkIcon.bitmap?.let {
+ val bitmap = Bitmap.createScaledBitmap(it, width, height, false)
+ BitmapDrawable(applicationContext.resources, bitmap)
+ }
+ } else {
+ null
+ }
+ }
+
+ companion object {
+ private const val TAG = "MediaRecommendationsViewModel"
+ private const val KEY_SMARTSPACE_ARTIST_NAME = "artist_name"
+ private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f
+ private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f
+ /**
+ * Delay duration is based on [GUTS_ANIMATION_DURATION], it should have 100 ms increase in
+ * order to let the animation end.
+ */
+ private const val GUTS_DISMISS_DELAY_MS_DURATION = 334L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
new file mode 100644
index 000000000000..d1713b5cd2fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.annotation.ColorInt
+import com.android.systemui.animation.Expandable
+
+/** Models UI state for media recommendations card. */
+data class MediaRecsCardViewModel(
+ val contentDescription: (Boolean) -> CharSequence,
+ @ColorInt val cardColor: Int,
+ @ColorInt val cardTitleColor: Int,
+ val onClicked: (Expandable) -> Unit,
+ val onLongClicked: () -> Unit,
+ val mediaRecs: List<MediaRecViewModel>,
+ val areTitlesVisible: Boolean,
+ val areSubtitlesVisible: Boolean,
+ val gutsMenu: GutsViewModel,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 2c25fe2ecb29..09f973cca343 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -99,15 +99,15 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
logger.log(MediaUiEvent.DISMISS_SWIPE)
}
- fun logLongPressOpen(uid: Int, packageName: String, instanceId: InstanceId) {
+ fun logLongPressOpen(uid: Int, packageName: String, instanceId: InstanceId?) {
logger.logWithInstanceId(MediaUiEvent.OPEN_LONG_PRESS, uid, packageName, instanceId)
}
- fun logLongPressDismiss(uid: Int, packageName: String, instanceId: InstanceId) {
+ fun logLongPressDismiss(uid: Int, packageName: String, instanceId: InstanceId?) {
logger.logWithInstanceId(MediaUiEvent.DISMISS_LONG_PRESS, uid, packageName, instanceId)
}
- fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
+ fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId?) {
logger.logWithInstanceId(
MediaUiEvent.OPEN_SETTINGS_LONG_PRESS,
uid,
@@ -188,7 +188,7 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
)
}
- fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) {
+ fun logRecommendationItemTap(packageName: String, instanceId: InstanceId?, position: Int) {
logger.logWithInstanceIdAndPosition(
MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP,
0,
@@ -198,7 +198,7 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
)
}
- fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) {
+ fun logRecommendationCardTap(packageName: String, instanceId: InstanceId?) {
logger.logWithInstanceId(
MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP,
0,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index d2471225a093..f08bc17c4f23 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -25,8 +25,8 @@ import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicPackageManagerAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -102,7 +102,7 @@ interface MediaProjectionAppSelectorModule {
@Binds
@MediaProjectionAppSelectorScope
- fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+ fun bindAppIconLoader(impl: BasicPackageManagerAppIconLoader): BasicAppIconLoader
@Binds
@IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
new file mode 100644
index 000000000000..ca5b5f842b25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.IconFactory
+import com.android.launcher3.util.UserIconInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class BadgedAppIconLoader
+@Inject
+constructor(
+ private val basicAppIconLoader: BasicAppIconLoader,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val context: Context,
+ private val iconFactoryProvider: Provider<IconFactory>,
+) {
+
+ suspend fun loadIcon(
+ userId: Int,
+ userType: RecentTask.UserType,
+ componentName: ComponentName
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+ val icon =
+ basicAppIconLoader.loadIcon(userId, componentName) ?: return@withContext null
+ val userHandler = UserHandle.of(userId)
+ val iconType = getIconType(userType)
+ val options =
+ BaseIconFactory.IconOptions().apply {
+ setUser(UserIconInfo(userHandler, iconType))
+ }
+ val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+ badgedIcon.newIcon(context)
+ }
+ }
+
+ private fun getIconType(userType: RecentTask.UserType): Int =
+ when (userType) {
+ RecentTask.UserType.CLONED -> UserIconInfo.TYPE_CLONED
+ RecentTask.UserType.WORK -> UserIconInfo.TYPE_WORK
+ RecentTask.UserType.PRIVATE -> UserIconInfo.TYPE_PRIVATE
+ RecentTask.UserType.STANDARD -> UserIconInfo.TYPE_MAIN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
index b85d6285c35b..03f6f015300f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
@@ -17,45 +17,29 @@
package com.android.systemui.mediaprojection.appselector.data
import android.content.ComponentName
-import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import com.android.launcher3.icons.BaseIconFactory.IconOptions
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.system.PackageManagerWrapper
import javax.inject.Inject
-import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-interface AppIconLoader {
+interface BasicAppIconLoader {
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
}
-class IconLoaderLibAppIconLoader
+class BasicPackageManagerAppIconLoader
@Inject
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val context: Context,
// Use wrapper to access hidden API that allows to get ActivityInfo for any user id
private val packageManagerWrapper: PackageManagerWrapper,
private val packageManager: PackageManager,
- private val iconFactoryProvider: Provider<IconFactory>
-) : AppIconLoader {
+) : BasicAppIconLoader {
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
withContext(backgroundDispatcher) {
- iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
- val activityInfo =
- packageManagerWrapper.getActivityInfo(component, userId)
- ?: return@withContext null
- val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
- val userHandler = UserHandle.of(userId)
- val options = IconOptions().apply { setUser(userHandler) }
- val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
- badgedIcon.newIcon(context)
- }
+ packageManagerWrapper.getActivityInfo(component, userId)?.loadIcon(packageManager)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index e9b458271ef7..3e9b546d58c9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -28,4 +28,12 @@ data class RecentTask(
val baseIntentComponent: ComponentName?,
@ColorInt val colorBackground: Int?,
val isForegroundTask: Boolean,
-)
+ val userType: UserType,
+) {
+ enum class UserType {
+ STANDARD,
+ WORK,
+ PRIVATE,
+ CLONED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5dde14bf0867..a6049c8b556d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -17,6 +17,8 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
+import android.content.pm.UserInfo
+import android.os.UserManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
@@ -41,7 +43,8 @@ constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
private val recentTasks: Optional<RecentTasks>,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val userManager: UserManager,
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -65,7 +68,8 @@ constructor(
it.topActivity,
it.baseIntent?.component,
it.taskDescription?.backgroundColor,
- isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
+ isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible,
+ userType = userManager.getUserInfo(it.userId).toUserType(),
)
}
}
@@ -81,4 +85,15 @@ constructor(
continuation.resume(tasks)
}
}
+
+ private fun UserInfo.toUserType(): RecentTask.UserType =
+ if (isCloneProfile) {
+ RecentTask.UserType.CLONED
+ } else if (isManagedProfile) {
+ RecentTask.UserType.WORK
+ } else if (isPrivateProfile) {
+ RecentTask.UserType.PRIVATE
+ } else {
+ RecentTask.UserType.STANDARD
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 3fe040a0d715..3b84d2c53a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -21,12 +21,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.android.systemui.res.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BadgedAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -39,7 +39,7 @@ class RecentTaskViewHolder
@AssistedInject
constructor(
@Assisted private val root: ViewGroup,
- private val iconLoader: AppIconLoader,
+ private val iconLoader: BadgedAppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
private val labelLoader: RecentTaskLabelLoader,
private val taskViewSizeProvider: TaskPreviewSizeProvider,
@@ -63,7 +63,7 @@ constructor(
scope.launch {
task.baseIntentComponent?.let { component ->
launch {
- val icon = iconLoader.loadIcon(task.userId, component)
+ val icon = iconLoader.loadIcon(task.userId, task.userType, component)
iconView.setImageDrawable(icon)
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
index 8aed535956b7..d87b612bf51b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.mediaprojection.devicepolicy
+import android.app.AlertDialog
+import android.content.Context
import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.res.Resources
import com.android.systemui.dagger.qualifiers.Main
@@ -23,22 +25,33 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
/** Dialog that shows that screen capture is disabled on this device. */
-class ScreenCaptureDisabledDialogDelegate @Inject constructor(
- @Main private val resources: Resources,
- private val systemUIDialogFactory: SystemUIDialog.Factory
-) : SystemUIDialog.Delegate {
+class ScreenCaptureDisabledDialogDelegate
+@Inject
+constructor(
+ private val context: Context,
+ @Main private val resources: Resources,
+) {
- override fun createDialog(): SystemUIDialog {
- val dialog = systemUIDialogFactory.create(this)
- dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ fun createPlainDialog(): AlertDialog {
+ return AlertDialog.Builder(context, R.style.Theme_SystemUI_Dialog).create().also {
+ initDialog(it)
+ }
+ }
+
+ fun createSysUIDialog(): AlertDialog {
+ return SystemUIDialog(context).also { initDialog(it) }
+ }
+
+ private fun initDialog(dialog: AlertDialog) {
+ dialog.setTitle(
+ resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title)
+ )
dialog.setMessage(
resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
)
dialog.setIcon(R.drawable.ic_cast)
- dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) {
- _, _ -> dialog.cancel()
+ dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) { _, _ ->
+ dialog.cancel()
}
-
- return dialog
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index dba0173afb1c..6224170fd906 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -47,6 +47,7 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
@DrawableRes private val dialogIconDrawable: Int? = null,
@ColorRes private val dialogIconTint: Int? = null,
+ @ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
) : DialogDelegate<T>, AdapterView.OnItemSelectedListener {
private lateinit var dialogTitle: TextView
private lateinit var startButton: TextView
@@ -55,7 +56,8 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
private lateinit var screenShareModeSpinner: Spinner
protected lateinit var dialog: AlertDialog
private var shouldLogCancel: Boolean = true
- var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
+ var selectedScreenShareOption: ScreenShareOption =
+ screenShareOptions.first { it.mode == defaultSelectedMode }
@CallSuper
override fun onStop(dialog: T) {
@@ -92,7 +94,7 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
}
private fun initScreenShareOptions() {
- selectedScreenShareOption = screenShareOptions.first()
+ selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
warning.text = warningText
initScreenShareSpinner()
}
@@ -118,6 +120,8 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
}
}
screenShareModeSpinner.isLongClickable = false
+ val defaultModePosition = screenShareOptions.indexOfFirst { it.mode == defaultSelectedMode }
+ screenShareModeSpinner.setSelection(defaultModePosition, /* animate= */ false)
}
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 17f9cafcb650..da9e00ddb6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -319,7 +319,7 @@ public class MediaProjectionPermissionActivity extends Activity
final UserHandle hostUserHandle = getHostUserHandle();
if (mScreenCaptureDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(hostUserHandle)) {
- AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog();
+ AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createPlainDialog();
setUpDialog(dialog);
dialog.show();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 4fe3a11078db..ade56c435421 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -85,6 +85,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -186,6 +187,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200;
private static final long AUTODIM_TIMEOUT_MS = 2250;
+ private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
private final Context mContext;
private final Bundle mSavedState;
@@ -223,6 +225,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final int mNavColorSampleMargin;
private EdgeBackGestureHandler mEdgeBackGestureHandler;
private NavigationBarFrame mFrame;
+ private MotionEvent mCurrentDownEvent;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -238,6 +241,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private int mLayoutDirection;
private Optional<Long> mHomeButtonLongPressDurationMs;
+ private Optional<Long> mOverrideHomeButtonLongPressDurationMs = Optional.empty();
+ private Optional<Float> mOverrideHomeButtonLongPressSlopMultiplier = Optional.empty();
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
private @Appearance int mAppearance;
@@ -405,6 +410,25 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ mOverrideHomeButtonLongPressDurationMs = Optional.of(duration)
+ .filter(value -> value > 0);
+ mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier)
+ .filter(value -> value > 0);
+ if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
+ Log.d(TAG, "Receive duration override: "
+ + mOverrideHomeButtonLongPressDurationMs.get());
+ }
+ if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
+ Log.d(TAG, "Receive slop multiplier override: "
+ + mOverrideHomeButtonLongPressSlopMultiplier.get());
+ }
+ if (mView != null) {
+ reconfigureHomeLongClick();
+ }
+ }
+
+ @Override
public void onHomeRotationEnabled(boolean enabled) {
mView.getRotationButtonController().setHomeRotationEnabled(enabled);
}
@@ -1016,7 +1040,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (mView.getHomeButton().getCurrentView() == null) {
return;
}
- if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
+ if (mHomeButtonLongPressDurationMs.isPresent()
+ || mOverrideHomeButtonLongPressDurationMs.isPresent()
+ || mOverrideHomeButtonLongPressSlopMultiplier.isPresent()
+ || !mLongPressHomeEnabled) {
mView.getHomeButton().getCurrentView().setLongClickable(false);
mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
mView.getHomeButton().setOnLongClickListener(null);
@@ -1038,6 +1065,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
pw.println(" mCurrentRotation=" + mCurrentRotation);
pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
+ pw.println(" mOverrideHomeButtonLongPressDurationMs="
+ + mOverrideHomeButtonLongPressDurationMs);
+ pw.println(" mOverrideHomeButtonLongPressSlopMultiplier="
+ + mOverrideHomeButtonLongPressSlopMultiplier);
pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled);
pw.println(" mNavigationBarWindowState="
+ windowStateToString(mNavigationBarWindowState));
@@ -1331,6 +1362,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
final Optional<CentralSurfaces> centralSurfacesOptional = mCentralSurfacesOptionalLazy.get();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
+ mCurrentDownEvent = MotionEvent.obtain(event);
mHomeBlockedThisTouch = false;
if (mTelecomManagerOptional.isPresent()
&& mTelecomManagerOptional.get().isRinging()) {
@@ -1342,9 +1377,45 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
}
if (mLongPressHomeEnabled) {
- mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
- mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
- });
+ if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
+ Log.d(TAG, "ACTION_DOWN Launcher override duration: "
+ + mOverrideHomeButtonLongPressDurationMs.get());
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ mOverrideHomeButtonLongPressDurationMs.get());
+ } else if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
+ // If override timeout doesn't exist but override touch slop exists, we use
+ // system default long press duration
+ Log.d(TAG, "ACTION_DOWN default duration: "
+ + ViewConfiguration.getLongPressTimeout());
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ ViewConfiguration.getLongPressTimeout());
+ } else {
+ mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+ Log.d(TAG, "ACTION_DOWN original duration: " + longPressDuration);
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ longPressDuration);
+ });
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) {
+ Log.w(TAG, "No callback. Don't handle touch slop.");
+ break;
+ }
+ float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f);
+ float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ float calculatedTouchSlop =
+ customSlopMultiplier * QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON * touchSlop;
+ float touchSlopSquared = calculatedTouchSlop * calculatedTouchSlop;
+
+ float dx = event.getX() - mCurrentDownEvent.getX();
+ float dy = event.getY() - mCurrentDownEvent.getY();
+ double distanceSquared = (dx * dx) + (dy * dy);
+ if (distanceSquared > touchSlopSquared) {
+ Log.i(TAG, "Touch slop passed. Abort.");
+ mView.abortCurrentGesture();
+ mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
}
break;
case MotionEvent.ACTION_UP:
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f8834af3ea4..9380d44dc27f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -281,5 +281,10 @@ constructor(
powerButtonLaunchGestureTriggeredDuringSleep = false,
)
}
+
+ /** Helper method for tests to simulate the device screen state change event. */
+ fun PowerInteractor.setScreenPowerState(screenPowerState: ScreenPowerState) {
+ this.onScreenPowerStateUpdated(screenPowerState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 3b3844a4be7b..e4249757d737 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -31,7 +31,7 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.util.LifecycleFragment;
import java.util.function.Consumer;
@@ -182,7 +182,7 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS {
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ MirrorController brightnessMirrorController) {
if (mQsImpl != null) {
mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a000d63a2ee3..a0607e9f859a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -68,7 +69,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.Utils;
@@ -544,7 +544,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ @Nullable MirrorController brightnessMirrorController) {
mQSPanelController.setBrightnessMirror(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cd6511979375..55dc4859cf90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -24,6 +24,8 @@ import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
@@ -38,9 +40,9 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.tuner.TunerService;
@@ -139,6 +141,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mBrightnessMirrorHandler.onQsPanelAttached();
PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
+ maybeReinflateBrightnessSlider();
}
@Override
@@ -157,15 +160,18 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Override
protected void onConfigurationChanged() {
mView.updateResources();
+ maybeReinflateBrightnessSlider();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ }
+
+ private void maybeReinflateBrightnessSlider() {
int newDensity = mView.getResources().getConfiguration().densityDpi;
if (newDensity != mLastDensity) {
mLastDensity = newDensity;
reinflateBrightnessSlider();
}
-
- if (mView.isListening()) {
- refreshAllTiles();
- }
}
private void reinflateBrightnessSlider() {
@@ -210,7 +216,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
}
}
- public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ public void setBrightnessMirror(@Nullable MirrorController brightnessMirrorController) {
mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
new file mode 100644
index 000000000000..8af566523b67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.flags
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NewQsUI {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_QS_UI_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.qsUiRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index b0707db0d02d..6eae32a0ffd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,6 +39,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -51,7 +52,6 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index dc42b5c35223..b27b974dc972 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -63,6 +63,7 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.DialogKt;
import java.util.ArrayList;
import java.util.List;
@@ -245,6 +246,10 @@ public class CastTile extends QSTileImpl<BooleanState> {
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG));
} else {
+ if (dialog.getWindow() != null) {
+ DialogKt.registerAnimationOnBackInvoked(dialog,
+ dialog.getWindow().getDecorView());
+ }
dialog.show();
}
});
@@ -272,7 +277,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
state.secondaryLabel = getDeviceName(device);
state.stateDescription = state.stateDescription + ","
+ mContext.getString(
- R.string.accessibility_cast_name, state.label);
+ R.string.accessibility_cast_name, state.label);
connecting = false;
break;
} else if (device.state == CastDevice.STATE_CONNECTING) {
@@ -342,14 +347,14 @@ public class CastTile extends QSTileImpl<BooleanState> {
};
private final SignalCallback mSignalCallback = new SignalCallback() {
- @Override
- public void setWifiIndicators(@NonNull WifiIndicators indicators) {
- // statusIcon.visible has the connected status information
- boolean enabledAndConnected = indicators.enabled
- && (indicators.qsIcon != null && indicators.qsIcon.visible);
- setCastTransportAllowed(enabledAndConnected);
- }
- };
+ @Override
+ public void setWifiIndicators(@NonNull WifiIndicators indicators) {
+ // statusIcon.visible has the connected status information
+ boolean enabledAndConnected = indicators.enabled
+ && (indicators.qsIcon != null && indicators.qsIcon.visible);
+ setCastTransportAllowed(enabledAndConnected);
+ }
+ };
private final HotspotController.Callback mHotspotCallback =
new HotspotController.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 2f8fe42672c2..3eeb2a3303be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -25,6 +25,7 @@ import android.os.Looper;
import android.provider.Settings;
import android.safetycenter.SafetyCenterManager;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.view.View;
import android.widget.Switch;
@@ -127,7 +128,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
} else {
state.secondaryLabel = mContext.getString(R.string.quick_settings_camera_mic_available);
}
- state.contentDescription = state.label;
+ state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel);
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index bc016bd221e9..065e89f10ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -88,6 +88,7 @@ constructor(
fun logUserActionRejectedByPolicy(
userAction: QSTileUserAction,
tileSpec: TileSpec,
+ restriction: String,
) {
tileSpec
.getLogBuffer()
@@ -95,7 +96,7 @@ constructor(
tileSpec.getLogTag(),
LogLevel.DEBUG,
{ str1 = userAction.toLogString() },
- { "tile $str1: rejected by policy" }
+ { "tile $str1: rejected by policy, restriction: $restriction" }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
index 45c6fffe6bd1..8782524cf250 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt
@@ -178,7 +178,8 @@ class QSTileViewModelImpl<DATA_TYPE>(
/**
* Creates a user input flow which:
* - filters false inputs with [falsingManager]
- * - takes care of a tile being disable by policy using [disabledByPolicyInteractor]
+ * - takes care of a tile being disable by policy using [disabledByPolicyInteractor]. The
+ * restrictions will be checked sequentially and the first one to block will be considered.
* - notifies [userActionInteractor] about the action
* - logs it accordingly using [qsTileLogger] and [qsTileAnalytics]
*
@@ -201,18 +202,22 @@ class QSTileViewModelImpl<DATA_TYPE>(
.onEach { userActionInteractor().handleInput(it.input) }
.flowOn(backgroundDispatcher)
+ /**
+ * The restrictions will be checked sequentially and the first one to block will be considered.
+ */
private fun Flow<QSTileUserAction>.filterByPolicy(user: UserHandle): Flow<QSTileUserAction> =
config.policy.let { policy ->
when (policy) {
is QSTilePolicy.NoRestrictions -> this@filterByPolicy
is QSTilePolicy.Restricted ->
filter { action ->
- val result =
- disabledByPolicyInteractor.isDisabled(user, policy.userRestriction)
- !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
- if (isDisabled) {
- qsTileLogger.logUserActionRejectedByPolicy(action, spec)
+ policy.userRestrictions.none {
+ val result = disabledByPolicyInteractor.isDisabled(user, it)
+ val handleResult = disabledByPolicyInteractor.handlePolicyResult(result)
+ if (handleResult) {
+ qsTileLogger.logUserActionRejectedByPolicy(action, spec, it)
}
+ handleResult
}
}
}
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 1410473acdfc..a531ee6edf97 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
@@ -1450,7 +1450,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
Intent getConfiguratorQrCodeGeneratorIntentOrNull(WifiEntry wifiEntry) {
if (!mFeatureFlags.isEnabled(Flags.SHARE_WIFI_QS_BUTTON) || wifiEntry == null
- || mWifiManager == null || !wifiEntry.canShare()) {
+ || mWifiManager == null || !wifiEntry.canShare()
+ || wifiEntry.getWifiConfiguration() == null) {
return null;
}
Intent intent = new Intent();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
new file mode 100644
index 000000000000..7117629622e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy
+
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.hardware.SensorPrivacyManager.Sensors.Sensor
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Observes SensorPrivacyToggle mode state changes providing the [SensorPrivacyToggleTileModel]. */
+class SensorPrivacyToggleTileDataInteractor
+@AssistedInject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val privacyController: IndividualSensorPrivacyController,
+ @Assisted @Sensor private val sensorId: Int,
+) : QSTileDataInteractor<SensorPrivacyToggleTileModel> {
+ @AssistedFactory
+ interface Factory {
+ fun create(@Sensor id: Int): SensorPrivacyToggleTileDataInteractor
+ }
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<SensorPrivacyToggleTileModel> =
+ conflatedCallbackFlow {
+ val callback =
+ IndividualSensorPrivacyController.Callback { sensor, blocked ->
+ if (sensor == sensorId) trySend(SensorPrivacyToggleTileModel(blocked))
+ }
+ privacyController.addCallback(callback) // does not emit an initial state
+ awaitClose { privacyController.removeCallback(callback) }
+ }
+ .onStart {
+ emit(SensorPrivacyToggleTileModel(privacyController.isSensorBlocked(sensorId)))
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+
+ override fun availability(user: UserHandle) =
+ flow { emit(isAvailable()) }.flowOn(bgCoroutineContext)
+
+ private suspend fun isAvailable(): Boolean {
+ return privacyController.supportsSensorToggle(sensorId) && isSensorDeviceConfigSet()
+ }
+
+ private suspend fun isSensorDeviceConfigSet(): Boolean =
+ withContext(bgCoroutineContext) {
+ try {
+ val deviceConfigName = getDeviceConfigName(sensorId)
+ return@withContext DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ deviceConfigName,
+ true
+ )
+ } catch (exception: IllegalArgumentException) {
+ Log.w(
+ TAG,
+ "isDeviceConfigSet for sensorId $sensorId: " +
+ "Defaulting to true due to exception. ",
+ exception
+ )
+ return@withContext true
+ }
+ }
+
+ private fun getDeviceConfigName(sensorId: Int): String {
+ if (sensorId == MICROPHONE) {
+ return "mic_toggle_enabled"
+ } else if (sensorId == CAMERA) {
+ return "camera_toggle_enabled"
+ } else {
+ throw IllegalArgumentException("getDeviceConfigName: unexpected sensorId: $sensorId")
+ }
+ }
+
+ private companion object {
+ const val TAG = "SensorPrivacyToggleTileException"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
new file mode 100644
index 000000000000..9711cb81e2c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager.Sensors.Sensor
+import android.hardware.SensorPrivacyManager.Sources.QS_TILE
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Handles sensor privacy toggle tile clicks and long clicks. */
+class SensorPrivacyToggleTileUserActionInteractor
+@AssistedInject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val activityStarter: ActivityStarter,
+ private val sensorPrivacyController: IndividualSensorPrivacyController,
+ private val safetyCenterManager: SafetyCenterManager,
+ @Assisted @Sensor private val sensorId: Int,
+) : QSTileUserActionInteractor<SensorPrivacyToggleTileModel> {
+ @AssistedFactory
+ interface Factory {
+ fun create(@Sensor id: Int): SensorPrivacyToggleTileUserActionInteractor
+ }
+
+ // should only be initialized in code known to run in background thread
+ private lateinit var longClickIntent: Intent
+
+ override suspend fun handleInput(input: QSTileInput<SensorPrivacyToggleTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ val blocked = input.data.isBlocked
+ if (
+ sensorPrivacyController.requiresAuthentication() &&
+ keyguardInteractor.isKeyguardDismissible.value &&
+ keyguardInteractor.isKeyguardShowing()
+ ) {
+ activityStarter.postQSRunnableDismissingKeyguard {
+ sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked)
+ }
+ return
+ }
+ sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked)
+ }
+ is QSTileUserAction.LongClick -> {
+ if (!::longClickIntent.isInitialized) {
+ longClickIntent =
+ Intent(
+ if (safetyCenterManager.isSafetyCenterEnabled) {
+ Settings.ACTION_PRIVACY_CONTROLS
+ } else {
+ Settings.ACTION_PRIVACY_SETTINGS
+ }
+ )
+ }
+ qsTileIntentUserActionHandler.handle(action.view, longClickIntent)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt
index 8a6376326660..04719afafa85 100644
--- a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model
-/** Constants that vary by compilation configuration. */
-public class Compile {
- /** Whether SystemUI was compiled in debug mode, and supports debug features */
- public static final boolean IS_DEBUG = false;
-}
+/**
+ * Sensor privacy toggle tile model.
+ *
+ * @param isBlocked is true when the sensor is blocked
+ */
+@JvmInline value class SensorPrivacyToggleTileModel(val isBlocked: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt
new file mode 100644
index 000000000000..2a9fd07a67cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import com.android.systemui.res.R
+
+sealed interface SensorPrivacyTileResources {
+ fun getIconRes(isBlocked: Boolean): Int
+ fun getTileLabelRes(): Int
+
+ data object CameraPrivacyTileResources : SensorPrivacyTileResources {
+ override fun getIconRes(isBlocked: Boolean): Int {
+ return if (isBlocked) {
+ R.drawable.qs_camera_access_icon_off
+ } else {
+ R.drawable.qs_camera_access_icon_on
+ }
+ }
+
+ override fun getTileLabelRes(): Int {
+ return R.string.quick_settings_camera_label
+ }
+ }
+
+ data object MicrophonePrivacyTileResources : SensorPrivacyTileResources {
+ override fun getIconRes(isBlocked: Boolean): Int {
+ return if (isBlocked) {
+ R.drawable.qs_mic_access_off
+ } else {
+ R.drawable.qs_mic_access_on
+ }
+ }
+
+ override fun getTileLabelRes(): Int {
+ return R.string.quick_settings_mic_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
new file mode 100644
index 000000000000..52622d26348d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Maps [SensorPrivacyToggleTileModel] to [QSTileState]. */
+class SensorPrivacyToggleTileMapper
+@AssistedInject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ @Assisted private val sensorPrivacyTileResources: SensorPrivacyTileResources,
+) : QSTileDataToStateMapper<SensorPrivacyToggleTileModel> {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ sensorPrivacyTileResources: SensorPrivacyTileResources
+ ): SensorPrivacyToggleTileMapper
+ }
+
+ override fun map(config: QSTileConfig, data: SensorPrivacyToggleTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(sensorPrivacyTileResources.getTileLabelRes())
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ sensorPrivacyTileResources.getIconRes(data.isBlocked),
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isBlocked) {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_blocked)
+ } else {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_available)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 18a4e2d26e89..e9e9d8b0bbfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -65,10 +65,10 @@ sealed interface QSTilePolicy {
data object NoRestrictions : QSTilePolicy
/**
- * Tile might be disabled by policy. [userRestriction] is usually a constant from
+ * Tile might be disabled by policy. Each item in [userRestrictions] is usually a constant from
* [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE].
* [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used
* to resolve this and show user a message when needed.
*/
- data class Restricted(val userRestriction: String) : QSTilePolicy
+ data class Restricted(val userRestrictions: List<String>) : QSTilePolicy
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 671050477042..3d86e3c084f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -34,6 +34,7 @@ import com.android.systemui.qs.QSContainerImpl
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
@@ -68,6 +69,9 @@ interface QSSceneAdapter {
*/
val qsView: Flow<View>
+ /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */
+ fun setBrightnessMirrorController(mirrorController: MirrorController?)
+
/**
* Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
* [qsView]. Re-inflations due to configuration changes will use the last used [context].
@@ -93,6 +97,9 @@ interface QSSceneAdapter {
val isQsFullyCollapsed: Boolean
get() = true
+ /** Request that the customizer be closed. Possibly animating it. */
+ fun requestCloseCustomizer()
+
sealed interface State {
val isVisible: Boolean
@@ -203,7 +210,7 @@ constructor(
applicationScope.launch {
launch {
state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
- _qsImpl.value?.apply {
+ qsImpl.value?.apply {
if (state != QSSceneAdapter.State.QS && customizing) {
this@apply.closeCustomizerImmediately()
}
@@ -277,6 +284,14 @@ constructor(
bottomNavBarSize.emit(padding)
}
+ override fun requestCloseCustomizer() {
+ qsImpl.value?.closeCustomizer()
+ }
+
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ qsImpl.value?.setBrightnessMirrorController(mirrorController)
+ }
+
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
setExpanded(state.isVisible && state.expansion > 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
new file mode 100644
index 000000000000..a3c2cbba1b1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -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 com.android.systemui.qs.ui.viewmodel
+
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsContainerViewModel
+@Inject
+constructor(
+ val brightnessSliderViewModel: BrightnessSliderViewModel,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index c695d4c98308..4c8647191cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -20,13 +20,13 @@ import androidx.lifecycle.LifecycleOwner
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
@@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.map
class QuickSettingsSceneViewModel
@Inject
constructor(
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
val notifications: NotificationsPlaceholderViewModel,
@@ -47,7 +48,9 @@ constructor(
val destinationScenes =
qsSceneAdapter.isCustomizing.map { customizing ->
if (customizing) {
- mapOf<UserAction, UserActionResult>(Back to UserActionResult(Scenes.QuickSettings))
+ // TODO(b/332749288) Empty map so there are no back handlers and back can close
+ // customizer
+ emptyMap()
// TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
// while customizing
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 7c1a2c032bea..4ece7b6c8990 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -99,6 +99,7 @@ import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
@@ -239,6 +240,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
} else {
mShadeViewControllerLazy.get().finishInputFocusTransfer(velocity);
}
+ } else if (action == ACTION_UP) {
+ // Gesture was too short to be picked up by scene container touch
+ // handling; programmatically start the transition to shade scene.
+ mSceneInteractor.get().changeScene(
+ Scenes.Shade, "short launcher swipe");
}
}
event.recycle();
@@ -259,6 +265,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ verifyCallerAndClearCallingIdentityPostMain("setOverrideHomeButtonLongPress",
+ () -> notifySetOverrideHomeButtonLongPress(duration, slopMultiplier));
+ }
+
+ @Override
public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
@@ -947,6 +959,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
+ private void notifySetOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).setOverrideHomeButtonLongPress(duration, slopMultiplier);
+ }
+ }
+
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1104,6 +1122,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
default void startAssistant(Bundle bundle) {}
default void setAssistantOverridesRequested(int[] invocationTypes) {}
default void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {}
+ /** Set override of home button long press duration and touch slop multiplier. */
+ default void setOverrideHomeButtonLongPress(long override, float slopMultiplier) {}
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 5e4919d44f23..4d34a869002a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -16,6 +16,7 @@
package com.android.systemui.recordissue
+import android.app.IActivityManager
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
@@ -51,7 +52,7 @@ class IssueRecordingService
@Inject
constructor(
controller: RecordingController,
- @LongRunning executor: Executor,
+ @LongRunning private val bgExecutor: Executor,
@Main handler: Handler,
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
@@ -60,10 +61,12 @@ constructor(
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
private val issueRecordingState: IssueRecordingState,
+ private val iActivityManager: IActivityManager,
+ private val launcherApps: LauncherApps,
) :
RecordingService(
controller,
- executor,
+ bgExecutor,
handler,
uiEventLogger,
notificationManager,
@@ -103,12 +106,26 @@ constructor(
// ViewCapture needs to save it's data before it is disabled, or else the data will
// be lost. This is expected to change in the near future, and when that happens
// this line should be removed.
- getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+ launcherApps.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
issueRecordingState.isRecording = false
}
ACTION_SHARE -> {
- shareRecording(intent)
+ bgExecutor.execute {
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
+
+ val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
+ if (issueRecordingState.takeBugReport) {
+ iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
+ } else {
+ shareRecording(screenRecording)
+ }
+ }
+
dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
panelInteractor.collapsePanels()
@@ -122,23 +139,17 @@ constructor(
return super.onStartCommand(intent, flags, startId)
}
- private fun shareRecording(intent: Intent) {
+ private fun shareRecording(screenRecording: Uri?) {
val sharableUri: Uri =
zipAndPackageRecordings(
TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- intent.getStringExtra(EXTRA_PATH)
+ screenRecording
)
?: return
val sendIntent =
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
-
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
{
@@ -150,7 +161,7 @@ constructor(
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+ private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -160,8 +171,8 @@ constructor(
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecordingUri != null) {
- contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+ if (screenRecording != null) {
+ contentResolver.openInputStream(screenRecording)?.use {
os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
it.transferTo(os)
os.closeEntry()
@@ -215,7 +226,7 @@ constructor(
fun getStartIntent(
context: Context,
screenRecord: Boolean,
- winscopeTracing: Boolean
+ winscopeTracing: Boolean,
): Intent =
Intent(context, IssueRecordingService::class.java)
.setAction(ACTION_START)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 394c5c2775a4..12ed06d75ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -25,6 +25,8 @@ class IssueRecordingState @Inject constructor() {
private val listeners = CopyOnWriteArrayList<Runnable>()
+ var takeBugReport: Boolean = false
+
var isRecording = false
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 832fc3f00022..68b88362427a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -63,6 +63,7 @@ constructor(
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+ private val issueRecordingState: IssueRecordingState,
@Assisted private val onStarted: Consumer<IssueRecordingConfig>,
) : SystemUIDialog.Delegate {
@@ -74,6 +75,7 @@ constructor(
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+ @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
private lateinit var issueTypeButton: Button
@MainThread
@@ -86,6 +88,7 @@ constructor(
setPositiveButton(
R.string.qs_record_issue_start,
{ _, _ ->
+ issueRecordingState.takeBugReport = bugReportSwitch.isChecked
onStarted.accept(
IssueRecordingConfig(
screenRecordSwitch.isChecked,
@@ -113,6 +116,7 @@ constructor(
bgExecutor.execute { onScreenRecordSwitchClicked() }
}
}
+ bugReportSwitch = requireViewById(R.id.bugreport_switch)
val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener {
@@ -131,7 +135,7 @@ constructor(
.isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
) {
mainExecutor.execute {
- screenCaptureDisabledDialogDelegate.createDialog().show()
+ screenCaptureDisabledDialogDelegate.createSysUIDialog().show()
screenRecordSwitch.isChecked = false
}
return
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b01216c22..3082eb904a51 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -24,6 +24,8 @@ import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.util.kotlin.WithPrev
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +36,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Source of truth for scene framework application state. */
@@ -44,7 +47,32 @@ constructor(
private val config: SceneContainerConfig,
private val dataSource: SceneDataSource,
) {
- val currentScene: StateFlow<SceneKey> = dataSource.currentScene
+ private val previousAndCurrentScene: StateFlow<WithPrev<SceneKey?, SceneKey>> =
+ dataSource.currentScene
+ .pairwise()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WithPrev(null, dataSource.currentScene.value),
+ )
+
+ val currentScene: StateFlow<SceneKey> =
+ previousAndCurrentScene
+ .map { it.newValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.newValue,
+ )
+
+ val previousScene: StateFlow<SceneKey?> =
+ previousAndCurrentScene
+ .map { it.previousValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.previousValue,
+ )
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 75bf131afdf9..02394552e8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -140,6 +140,14 @@ constructor(
)
/**
+ * The previous scene.
+ *
+ * This is effectively the previous value of [currentScene] which means that all caveats, for
+ * example regarding when in a transition the current scene changes, apply.
+ */
+ val previousScene: StateFlow<SceneKey?> = repository.previousScene
+
+ /**
* Returns the keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
@@ -162,7 +170,9 @@ constructor(
loggingReason: String,
transitionKey: TransitionKey? = null,
) {
- check(toScene != Scenes.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
+ check(
+ toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
+ ) {
"Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
" change was: $loggingReason"
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f4292207f3a9..32d72e0bac22 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -34,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SysUiState
@@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import dagger.Lazy
@@ -83,6 +85,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: SceneInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val bouncerInteractor: BouncerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val flags: SceneContainerFlags,
@@ -194,39 +197,41 @@ constructor(
}
}
applicationScope.launch {
- simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked ->
- val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
- val isUnlocked = deviceEntryInteractor.isUnlocked.value
-
- when {
- isAnySimLocked -> {
- switchToScene(
- targetSceneKey = Scenes.Bouncer,
- loggingReason = "Need to authenticate locked SIM card."
- )
- }
- isUnlocked && canSwipeToEnter == false -> {
- switchToScene(
- targetSceneKey = Scenes.Gone,
- loggingReason =
- "All SIM cards unlocked and device already" +
- " unlocked and lockscreen doesn't require a swipe to dismiss."
- )
- }
- else -> {
- switchToScene(
- targetSceneKey = Scenes.Lockscreen,
- loggingReason =
- "All SIM cards unlocked and device still locked" +
- " or lockscreen still requires a swipe to dismiss."
- )
+ simBouncerInteractor
+ .get()
+ .isAnySimSecure
+ .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair)
+ .collect { (isAnySimLocked, unlockStatus) ->
+ when {
+ isAnySimLocked -> {
+ switchToScene(
+ targetSceneKey = Scenes.Bouncer,
+ loggingReason = "Need to authenticate locked SIM card."
+ )
+ }
+ unlockStatus.isUnlocked &&
+ deviceEntryInteractor.canSwipeToEnter.value == false -> {
+ switchToScene(
+ targetSceneKey = Scenes.Gone,
+ loggingReason =
+ "All SIM cards unlocked and device already unlocked and " +
+ "lockscreen doesn't require a swipe to dismiss."
+ )
+ }
+ else -> {
+ switchToScene(
+ targetSceneKey = Scenes.Lockscreen,
+ loggingReason =
+ "All SIM cards unlocked and device still locked" +
+ " or lockscreen still requires a swipe to dismiss."
+ )
+ }
}
}
- }
}
applicationScope.launch {
- deviceEntryInteractor.isUnlocked
- .mapNotNull { isUnlocked ->
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .mapNotNull { deviceUnlockStatus ->
val renderedScenes =
when (val transitionState = sceneInteractor.transitionState.value) {
is ObservableTransitionState.Idle -> setOf(transitionState.scene)
@@ -238,7 +243,7 @@ constructor(
}
val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
val isOnBouncer = renderedScenes.contains(Scenes.Bouncer)
- if (!isUnlocked) {
+ if (!deviceUnlockStatus.isUnlocked) {
return@mapNotNull if (isOnLockscreen || isOnBouncer) {
// Already on lockscreen or bouncer, no need to change scenes.
null
@@ -250,8 +255,6 @@ constructor(
}
}
- val isBypassEnabled = deviceEntryInteractor.isBypassEnabled.value
- val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
when {
isOnBouncer ->
// When the device becomes unlocked in Bouncer, go to Gone.
@@ -266,14 +269,12 @@ constructor(
// when the unlock state changes indicates this is an active
// authentication attempt.
when {
- isBypassEnabled ->
- Scenes.Gone to
- "device has been unlocked on lockscreen with bypass" +
- " enabled"
- canSwipeToEnter == false ->
+ deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen ==
+ true ->
Scenes.Gone to
- "device has been unlocked on lockscreen using an active" +
- " authentication mechanism"
+ "device has been unlocked on lockscreen with bypass " +
+ "enabled or using an active authentication " +
+ "mechanism: ${deviceUnlockStatus.deviceUnlockSource}"
else -> null
}
// Not on lockscreen or bouncer, so remain in the current scene.
@@ -297,7 +298,7 @@ constructor(
)
} else {
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
- val isUnlocked = deviceEntryInteractor.isUnlocked.value
+ val isUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
if (isUnlocked && canSwipeToEnter == false) {
val isTransitioningToLockscreen =
sceneInteractor.transitioningTo.value == Scenes.Lockscreen
@@ -429,8 +430,8 @@ constructor(
/** Keeps the interaction state of [CentralSurfaces] up-to-date. */
private fun hydrateInteractionState() {
applicationScope.launch {
- deviceEntryInteractor.isUnlocked
- .map { !it }
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .map { !it.isUnlocked }
.flatMapLatest { isDeviceLocked ->
if (isDeviceLocked) {
sceneInteractor.transitionState
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 9e27dad0ea73..c9291966bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -28,6 +28,8 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
import dagger.Module
import dagger.Provides
@@ -40,11 +42,13 @@ object SceneContainerFlag {
inline val isEnabled
get() =
sceneContainer() && // mainAconfigFlag
- KeyguardBottomAreaRefactor.isEnabled &&
- MigrateClocksToBlueprint.isEnabled &&
- ComposeLockscreen.isEnabled &&
+ ComposeLockscreen.isEnabled &&
+ KeyguardBottomAreaRefactor.isEnabled &&
+ KeyguardWmStateRefactor.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
- KeyguardWmStateRefactor.isEnabled
+ MigrateClocksToBlueprint.isEnabled &&
+ NotificationsHeadsUpRefactor.isEnabled &&
+ PredictiveBackSysUiFlag.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/** The main aconfig flag. */
@@ -53,11 +57,13 @@ object SceneContainerFlag {
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
+ ComposeLockscreen.token,
KeyguardBottomAreaRefactor.token,
- MigrateClocksToBlueprint.token,
KeyguardWmStateRefactor.token,
- ComposeLockscreen.token,
MediaInSceneContainerFlag.token,
+ MigrateClocksToBlueprint.token,
+ NotificationsHeadsUpRefactor.token,
+ PredictiveBackSysUiFlag.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 8fe84c98866b..3dc207063060 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -164,7 +164,7 @@ public class RecordingController
if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
&& mDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
- return mScreenCaptureDisabledDialogDelegate.createDialog();
+ return mScreenCaptureDisabledDialogDelegate.createSysUIDialog();
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b2c01e180ec4..cbb61b37b7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -206,7 +206,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
break;
case ACTION_SHARE:
- Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
+ Uri shareUri = intent.getParcelableExtra(EXTRA_PATH, Uri.class);
Intent shareIntent = new Intent(Intent.ACTION_SEND)
.setType("video/mp4")
@@ -356,7 +356,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
PendingIntent.getService(
this,
REQUEST_CODE,
- getShareIntent(this, uri != null ? uri.toString() : null),
+ getShareIntent(this, uri),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build();
@@ -512,7 +512,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
}
- private Intent getShareIntent(Context context, String path) {
+ private Intent getShareIntent(Context context, Uri path) {
return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index ba775cd3cd82..1c76b00f7f05 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -18,6 +18,7 @@ package com.android.systemui.screenrecord
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
@@ -35,12 +36,15 @@ import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Switch
import androidx.annotation.LayoutRes
+import androidx.annotation.StyleRes
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.BaseMediaProjectionPermissionDialogDelegate
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
import com.android.systemui.mediaprojection.permission.SINGLE_APP
+import com.android.systemui.mediaprojection.permission.ScreenShareMode
import com.android.systemui.mediaprojection.permission.ScreenShareOption
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -51,15 +55,18 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
/** Dialog to select screen recording options */
-class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
- @Assisted private val hostUserHandle: UserHandle,
- @Assisted private val hostUid: Int,
- @Assisted private val controller: RecordingController,
+class ScreenRecordPermissionDialogDelegate(
+ private val hostUserHandle: UserHandle,
+ private val hostUid: Int,
+ private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
- @Assisted private val onStartRecordingClicked: Runnable?,
+ private val onStartRecordingClicked: Runnable?,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val systemUIDialogFactory: SystemUIDialog.Factory,
+ @ScreenShareMode defaultSelectedMode: Int,
+ @StyleRes private val theme: Int,
+ private val context: Context,
) :
BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
createOptionList(),
@@ -67,9 +74,34 @@ class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
hostUid = hostUid,
mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
- R.color.screenrecord_icon_color
- ), SystemUIDialog.Delegate {
-
+ R.color.screenrecord_icon_color,
+ defaultSelectedMode,
+ ),
+ SystemUIDialog.Delegate {
+ @AssistedInject
+ constructor(
+ @Assisted hostUserHandle: UserHandle,
+ @Assisted hostUid: Int,
+ @Assisted controller: RecordingController,
+ activityStarter: ActivityStarter,
+ userContextProvider: UserContextProvider,
+ @Assisted onStartRecordingClicked: Runnable?,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+ systemUIDialogFactory: SystemUIDialog.Factory,
+ @Application context: Context,
+ ) : this(
+ hostUserHandle,
+ hostUid,
+ controller,
+ activityStarter,
+ userContextProvider,
+ onStartRecordingClicked,
+ mediaProjectionMetricsLogger,
+ systemUIDialogFactory,
+ defaultSelectedMode = SINGLE_APP,
+ theme = SystemUIDialog.DEFAULT_THEME,
+ context,
+ )
@AssistedFactory
interface Factory {
@@ -77,7 +109,7 @@ class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
recordingController: RecordingController,
hostUserHandle: UserHandle,
hostUid: Int,
- onStartRecordingClicked: Runnable?
+ onStartRecordingClicked: Runnable?,
): ScreenRecordPermissionDialogDelegate
}
@@ -89,7 +121,7 @@ class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
private lateinit var options: Spinner
override fun createDialog(): SystemUIDialog {
- return systemUIDialogFactory.create(this)
+ return systemUIDialogFactory.create(this, context, theme)
}
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
new file mode 100644
index 000000000000..caa67dff086f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.ExitTransitionCoordinator
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.UserHandle
+import android.util.Log
+import android.util.Pair
+import android.view.View
+import android.view.Window
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+
+class ActionExecutor
+@AssistedInject
+constructor(
+ private val intentExecutor: ActionIntentExecutor,
+ @Application private val applicationScope: CoroutineScope,
+ @Assisted val window: Window,
+ @Assisted val transitionView: View,
+ @Assisted val onDismiss: (() -> Unit)
+) {
+
+ var isPendingSharedTransition = false
+ private set
+
+ fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
+ isPendingSharedTransition = true
+ val windowTransition = createWindowTransition()
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ intentExecutor.launchIntent(
+ intent,
+ user,
+ overrideTransition,
+ windowTransition.first,
+ windowTransition.second
+ )
+ }
+ }
+
+ fun sendPendingIntent(pendingIntent: PendingIntent) {
+ try {
+ val options = BroadcastOptions.makeBasic()
+ options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ pendingIntent.send(options.toBundle())
+ onDismiss.invoke()
+ } catch (e: PendingIntent.CanceledException) {
+ Log.e(TAG, "Intent cancelled", e)
+ }
+ }
+
+ /**
+ * Supplies the necessary bits for the shared element transition to share sheet. Note that once
+ * called, the action intent to share must be sent immediately after.
+ */
+ private fun createWindowTransition(): Pair<ActivityOptions, ExitTransitionCoordinator> {
+ val callbacks: ExitTransitionCallbacks =
+ object : ExitTransitionCallbacks {
+ override fun isReturnTransitionAllowed(): Boolean {
+ return false
+ }
+
+ override fun hideSharedElements() {
+ isPendingSharedTransition = false
+ onDismiss.invoke()
+ }
+
+ override fun onFinish() {}
+ }
+ return ActivityOptions.startSharedElementAnimation(
+ window,
+ callbacks,
+ null,
+ Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+ }
+
+ companion object {
+ private const val TAG = "ActionExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 8e9769ab7d0e..a0cef529ecde 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,7 +23,9 @@ import android.content.ContentProvider
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.UserHandle
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity
object ActionIntentCreator {
/** @return a chooser intent to share the given URI. */
@@ -89,6 +91,14 @@ object ActionIntentCreator {
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+ /** @return an Intent to start the LongScreenshotActivity */
+ fun createLongScreenshotIntent(owner: UserHandle, context: Context): Intent {
+ return Intent(context, LongScreenshotActivity::class.java)
+ .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ }
+
private const val EXTRA_EDIT_SOURCE = "edit_source"
private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1f9853b17a28..4eca51d47a36 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -25,7 +25,6 @@ import android.os.Process.myUserHandle
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
-import android.util.Pair
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -67,20 +66,22 @@ constructor(
*/
fun launchIntentAsync(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
applicationScope.launch("$TAG#launchIntentAsync") {
- launchIntent(intent, transition, user, overrideTransition)
+ launchIntent(intent, user, overrideTransition, options, transitionCoordinator)
}
}
suspend fun launchIntent(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
if (screenshotActionDismissSystemWindows()) {
keyguardController.dismiss()
@@ -90,14 +91,12 @@ constructor(
} else {
dismissKeyguard()
}
- transition?.second?.startExit()
+ transitionCoordinator?.startExit()
if (user == myUserHandle()) {
- withContext(mainDispatcher) {
- context.startActivity(intent, transition?.first?.toBundle())
- }
+ withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
} else {
- launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
+ launchCrossProfileIntent(user, intent, options?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index a1481f6d6d2b..4cf18fb482d8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -34,9 +34,9 @@ import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -113,7 +113,7 @@ constructor(
override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
view.setChipIntents(imageData)
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
if (DEBUG_DISMISS) {
Log.d(TAG, "screenshot dismissal requested")
}
@@ -124,7 +124,7 @@ constructor(
}
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(event, 0, packageName) }
view.animateDismissal()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index bbf7ed529220..49144091cb62 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -165,6 +165,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
mParams.owner);
mImageData.subject = getSubjectString(mImageTime);
+ mImageData.imageTime = mImageTime;
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 97acccde2524..f69021f34ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -16,78 +16,180 @@
package com.android.systemui.screenshot
+import android.app.assist.AssistContent
import android.content.Context
-import android.content.Intent
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
+import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
-import javax.inject.Inject
+import com.android.systemui.screenshot.ActionIntentCreator.createEdit
+import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
* implementation.
*/
interface ScreenshotActionsProvider {
- data class ScreenshotAction(
- val icon: Drawable? = null,
- val text: String? = null,
- val description: String,
- val overrideTransition: Boolean = false,
- val retrieveIntent: (Uri) -> Intent
- )
+ fun onScrollChipReady(onClick: Runnable)
+ fun setCompletedScreenshot(result: ScreenshotSavedResult)
- interface ScreenshotActionsCallback {
- fun setPreviewAction(overrideTransition: Boolean = false, retrieveIntent: (Uri) -> Intent)
- fun addAction(action: ScreenshotAction) = addActions(listOf(action))
- fun addActions(actions: List<ScreenshotAction>)
- }
+ fun onAssistContentAvailable(assistContent: AssistContent) {}
interface Factory {
fun create(
- context: Context,
- user: UserHandle?,
- callback: ScreenshotActionsCallback
+ request: ScreenshotData,
+ requestId: String,
+ actionExecutor: ActionExecutor,
): ScreenshotActionsProvider
}
}
-class DefaultScreenshotActionsProvider(
+class DefaultScreenshotActionsProvider
+@AssistedInject
+constructor(
private val context: Context,
- private val user: UserHandle?,
- private val callback: ScreenshotActionsProvider.ScreenshotActionsCallback
+ private val viewModel: ScreenshotViewModel,
+ private val smartActionsProvider: SmartActionsProvider,
+ private val uiEventLogger: UiEventLogger,
+ @Assisted val request: ScreenshotData,
+ @Assisted val requestId: String,
+ @Assisted val actionExecutor: ActionExecutor,
) : ScreenshotActionsProvider {
+ private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var result: ScreenshotSavedResult? = null
+
init {
- callback.setPreviewAction(true) { ActionIntentCreator.createEdit(it, context) }
- val editAction =
- ScreenshotActionsProvider.ScreenshotAction(
+ viewModel.setPreviewAction {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
+ uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
+ }
+ viewModel.addAction(
+ ActionButtonViewModel(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
context.resources.getString(R.string.screenshot_edit_label),
context.resources.getString(R.string.screenshot_edit_description),
- true
- ) { uri ->
- ActionIntentCreator.createEdit(uri, context)
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
}
- val shareAction =
- ScreenshotActionsProvider.ScreenshotAction(
+ )
+ viewModel.addAction(
+ ActionButtonViewModel(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
context.resources.getString(R.string.screenshot_share_description),
- false
- ) { uri ->
- ActionIntentCreator.createShare(uri)
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+ uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createShareWithSubject(result.uri, result.subject),
+ result.user,
+ false
+ )
+ }
+ }
+ )
+ smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
+ if (!quickShare.actionIntent.isImmutable) {
+ viewModel.addAction(
+ ActionButtonViewModel(
+ quickShare.getIcon().loadDrawable(context),
+ quickShare.title,
+ quickShare.title
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
+ onDeferrableActionTapped { result ->
+ uiEventLogger.log(
+ SCREENSHOT_SMART_ACTION_TAPPED,
+ 0,
+ request.packageNameString
+ )
+ val pendingIntentWithUri =
+ smartActionsProvider.wrapIntent(
+ quickShare,
+ result.uri,
+ result.subject,
+ requestId
+ )
+ actionExecutor.sendPendingIntent(pendingIntentWithUri)
+ }
+ }
+ )
+ } else {
+ Log.w(TAG, "Received immutable quick share pending intent; ignoring")
}
- callback.addActions(listOf(editAction, shareAction))
+ }
}
- class Factory @Inject constructor() : ScreenshotActionsProvider.Factory {
- override fun create(
- context: Context,
- user: UserHandle?,
- callback: ScreenshotActionsProvider.ScreenshotActionsCallback
- ): ScreenshotActionsProvider {
- return DefaultScreenshotActionsProvider(context, user, callback)
+ override fun onScrollChipReady(onClick: Runnable) {
+ viewModel.addAction(
+ ActionButtonViewModel(
+ AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ ) {
+ onClick.run()
+ }
+ )
+ }
+
+ override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
+ if (this.result != null) {
+ Log.e(TAG, "Got a second completed screenshot for existing request!")
+ return
+ }
+ this.result = result
+ pendingAction?.invoke(result)
+ smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
+ viewModel.addActions(
+ smartActions.map {
+ ActionButtonViewModel(it.getIcon().loadDrawable(context), it.title, it.title) {
+ actionExecutor.sendPendingIntent(it.actionIntent)
+ }
+ }
+ )
}
}
+
+ private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
+ result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
+ }
+
+ @AssistedFactory
+ interface Factory : ScreenshotActionsProvider.Factory {
+ override fun create(
+ request: ScreenshotData,
+ requestId: String,
+ actionExecutor: ActionExecutor,
+ ): DefaultScreenshotActionsProvider
+ }
+
+ companion object {
+ private const val TAG = "ScreenshotActionsProvider"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 047ecb42287b..13dd229d8f62 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,7 +34,6 @@ import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
@@ -51,7 +50,6 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,17 +57,12 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.widget.Toast;
import android.window.WindowContext;
@@ -84,10 +77,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
-import com.android.systemui.screenshot.scroll.LongScreenshotData;
-import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -96,13 +86,13 @@ import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import kotlin.Unit;
+
import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
+import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.inject.Provider;
@@ -114,34 +104,6 @@ import javax.inject.Provider;
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
- private ScrollCaptureResponse mLastScrollCaptureResponse;
- private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
-
- /**
- * This is effectively a no-op, but we need something non-null to pass in, in order to
- * successfully override the pending activity entrance animation.
- */
- static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(
- @WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e(TAG, "Error finishing screenshot remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- };
-
/**
* POD used in the AsyncTask which saves an image in the background.
*/
@@ -167,6 +129,7 @@ public class ScreenshotController {
public Notification.Action quickShareAction;
public UserHandle owner;
public String subject; // Title for sharing
+ public Long imageTime; // Time at which screenshot was saved
/**
* Used to reset the return data on error
@@ -176,6 +139,7 @@ public class ScreenshotController {
smartActions = null;
quickShareAction = null;
subject = null;
+ imageTime = null;
}
}
@@ -237,22 +201,20 @@ public class ScreenshotController {
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ActionExecutor mActionExecutor;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
private final int mDisplayId;
- private final ScrollCaptureController mScrollCaptureController;
- private final LongScreenshotData mLongScreenshotHolder;
- private final boolean mIsLowRamDevice;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
- private final ActionIntentExecutor mActionExecutor;
+ private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
@@ -261,11 +223,9 @@ public class ScreenshotController {
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
private boolean mBlockAttach;
-
- private ScreenshotActionsProvider mActionsProvider;
-
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
+ private ScreenshotActionsProvider mActionsProvider;
private String mPackageName = "";
private final BroadcastReceiver mCopyBroadcastReceiver;
@@ -296,19 +256,17 @@ public class ScreenshotController {
ScreenshotActionsProvider.Factory actionsProviderFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
- ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
ImageCapture imageCapture,
@Main Executor mainExecutor,
- ScrollCaptureController scrollCaptureController,
- LongScreenshotData longScreenshotHolder,
- ActivityManager activityManager,
+ ScrollCaptureExecutor scrollCaptureExecutor,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
- ActionIntentExecutor actionExecutor,
+ ActionIntentExecutor actionIntentExecutor,
+ ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
@@ -317,15 +275,13 @@ public class ScreenshotController {
@Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
+ mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
- mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
- mScrollCaptureController = scrollCaptureController;
- mLongScreenshotHolder = longScreenshotHolder;
- mIsLowRamDevice = activityManager.isLowRamDevice();
+ mScrollCaptureExecutor = scrollCaptureExecutor;
mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
@@ -341,13 +297,12 @@ public class ScreenshotController {
final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mFlags = flags;
- mActionExecutor = actionExecutor;
+ mActionIntentExecutor = actionIntentExecutor;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
- mActionsProviderFactory = actionsProviderFactory;
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -366,6 +321,12 @@ public class ScreenshotController {
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+ () -> {
+ requestDismissal(null);
+ return Unit.INSTANCE;
+ });
+
// Sound is only reproduced from the controller of the default display.
if (displayId == Display.DEFAULT_DISPLAY) {
mScreenshotSoundController = screenshotSoundController.get();
@@ -392,10 +353,10 @@ public class ScreenshotController {
Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ && screenshot.getBitmap() == null) {
Rect bounds = getFullScreenRect();
- screenshot.setBitmap(
- mImageCapture.captureDisplay(mDisplayId, bounds));
+ screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds));
screenshot.setScreenBounds(bounds);
}
@@ -441,8 +402,23 @@ public class ScreenshotController {
return;
}
- saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
- this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ if (screenshotShelfUi()) {
+ final UUID requestId = UUID.randomUUID();
+ final String screenshotId = String.format("Screenshot_%s", requestId);
+ mActionsProvider = mActionsProviderFactory.create(
+ screenshot, screenshotId, mActionExecutor);
+ saveScreenshotInBackground(screenshot, requestId, finisher);
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ assistContent -> {
+ mActionsProvider.onAssistContentAvailable(assistContent);
+ });
+ }
+ } else {
+ saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
+ this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ }
// The window is focusable by default
setWindowFocusable(true);
@@ -477,7 +453,9 @@ public class ScreenshotController {
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- mScreenshotHandler.cancelTimeout(); // restarted after animation
+ if (!screenshotShelfUi()) {
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
+ }
}
private boolean shouldShowUi() {
@@ -497,11 +475,6 @@ public class ScreenshotController {
mViewProxy.reset();
- if (screenshotShelfUi()) {
- mActionsProvider = mActionsProviderFactory.create(mContext, screenshot.getUserHandle(),
- ((ScreenshotActionsProvider.ScreenshotActionsCallback) mViewProxy));
- }
-
if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (!mViewProxy.isDismissing()) {
@@ -529,7 +502,11 @@ public class ScreenshotController {
}
boolean isPendingSharedTransition() {
- return mViewProxy.isPendingSharedTransition();
+ if (screenshotShelfUi()) {
+ return mActionExecutor.isPendingSharedTransition();
+ } else {
+ return mViewProxy.isPendingSharedTransition();
+ }
}
// Any cleanup needed when the service is being destroyed.
@@ -577,8 +554,9 @@ public class ScreenshotController {
@Override
public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- mActionExecutor.launchIntentAsync(
- intent, createWindowTransition(), owner, overrideTransition);
+ Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
+ mActionIntentExecutor.launchIntentAsync(
+ intent, owner, overrideTransition, exit.first, exit.second);
}
@Override
@@ -615,9 +593,8 @@ public class ScreenshotController {
mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(() -> {
- requestScrollCapture(owner);
- }, 150);
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(owner), 150);
mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
@@ -640,118 +617,51 @@ public class ScreenshotController {
}
private void requestScrollCapture(UserHandle owner) {
- if (!allowLongScreenshots()) {
- Log.d(TAG, "Long screenshots not supported on this device");
- return;
- }
- mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
- mDisplayId);
- mLastScrollCaptureRequest = future;
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future, owner), mMainExecutor);
- }
-
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
- UserHandle owner) {
- try {
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (responseFuture.isCancelled()) {
- return;
- }
- mLastScrollCaptureResponse = responseFuture.get();
- if (!mLastScrollCaptureResponse.isConnected()) {
- // No connection means that the target window wasn't found
- // or that it cannot support scroll capture.
- Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
- return;
- }
- Log.d(TAG, "ScrollCapture: connected to window ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
-
- final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
- Bitmap newScreenshot =
- mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
-
- if (newScreenshot != null) {
- // delay starting scroll capture to make sure scrim is up before the app moves
- mViewProxy.prepareScrollingTransition(
- response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
- () -> runBatchScrollCapture(response, owner));
- } else {
- Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplayId,
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ if (screenshotShelfUi() && mActionsProvider != null) {
+ mActionsProvider.onScrollChipReady(
+ () -> onScrollButtonClicked(owner, response));
+ } else {
+ mViewProxy.showScrollChip(response.getPackageName(),
+ () -> onScrollButtonClicked(owner, response));
+ }
+ return Unit.INSTANCE;
}
- });
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "requestScrollCapture failed", e);
- }
+ );
}
- ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
-
- private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
- // Clear the reference to prevent close() in dismissScreenshot
- mLastScrollCaptureResponse = null;
-
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
}
- mLongScreenshotFuture = mScrollCaptureController.run(response);
- mLongScreenshotFuture.addListener(() -> {
- ScrollCaptureController.LongScreenshot longScreenshot;
- try {
- longScreenshot = mLongScreenshotFuture.get();
- } catch (CancellationException e) {
- Log.e(TAG, "Long screenshot cancelled");
- return;
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "Exception", e);
- mViewProxy.restoreNonScrollingUi();
- return;
- }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
+ return;
+ }
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
+ }
- if (longScreenshot.getHeight() == 0) {
- mViewProxy.restoreNonScrollingUi();
- return;
- }
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mActionIntentExecutor.launchIntentAsync(intent, owner, true,
+ ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
- mLongScreenshotHolder.setLongScreenshot(longScreenshot);
- mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) -> {
- mViewProxy.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot);
- // TODO: Do this via ActionIntentExecutor instead.
- mContext.closeSystemDialogs();
- }
- );
-
- final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
- owner);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- mContext.startActivity(intent,
- ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner,
- mDisplayId);
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }, mMainExecutor);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
}
private void withWindowAttached(Runnable action) {
@@ -896,17 +806,7 @@ public class ScreenshotController {
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- mLastScrollCaptureRequest = null;
- }
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
+ mScrollCaptureExecutor.close();
if (mCurrentRequestCallback != null) {
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
@@ -916,6 +816,35 @@ public class ScreenshotController {
mScreenshotHandler.cancelTimeout();
}
+ private void saveScreenshotInBackground(
+ ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
+ ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
+ future.addListener(() -> {
+ try {
+ ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
+ logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
+ mScreenshotHandler.resetTimeout();
+ if (result.uri != null) {
+ mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp));
+ }
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ + "finisher.accept(\"" + result.uri + "\"");
+ }
+ finisher.accept(result.uri);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to store screenshot", e);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
+ }
+ finisher.accept(null);
+ }
+ }, mMainExecutor);
+ }
+
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
@@ -951,13 +880,12 @@ public class ScreenshotController {
*/
private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
logSuccessOnActionsReady(imageData);
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI actions");
- }
-
mScreenshotHandler.resetTimeout();
if (imageData.uri != null) {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Showing UI actions");
+ }
if (!imageData.owner.equals(Process.myUserHandle())) {
Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+ imageData.uri);
@@ -1005,20 +933,27 @@ public class ScreenshotController {
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
- private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
- if (imageData.uri == null) {
+ private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
+ if (uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
- if (mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ if (mUserManager.isManagedProfile(owner.getIdentifier())) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
mPackageName);
}
}
}
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ logScreenshotResultStatus(imageData.uri, imageData.owner);
+ }
+
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
@@ -1055,10 +990,6 @@ public class ScreenshotController {
return mDisplayManager.getDisplay(mDisplayId);
}
- private boolean allowLongScreenshots() {
- return !mIsLowRamDevice;
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index 92e933a9557b..4fdd90bdcded 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -5,6 +5,7 @@ import android.graphics.Bitmap
import android.graphics.Insets
import android.graphics.Rect
import android.net.Uri
+import android.os.Process
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager.ScreenshotSource
@@ -31,6 +32,10 @@ data class ScreenshotData(
val packageNameString: String
get() = if (topComponent == null) "" else topComponent!!.packageName
+ fun getUserOrDefault(): UserHandle {
+ return userHandle ?: Process.myUserHandle()
+ }
+
companion object {
@JvmStatic
fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 796457d88cc2..3ad4075a2b89 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -17,13 +17,14 @@
package com.android.systemui.screenshot
/** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
+fun interface ScreenshotRequestProcessor {
/**
* Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
*
- * @param screenshot the screenshot to process
+ * @param original the screenshot to process
+ * @return a potentially modified screenshot data
*/
- suspend fun process(screenshot: ScreenshotData): ScreenshotData
+ suspend fun process(original: ScreenshotData): ScreenshotData
}
/** Exception thrown by [RequestProcessor] if something goes wrong. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
new file mode 100644
index 000000000000..5b6e7ac3e4e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.net.Uri
+import android.os.UserHandle
+import java.text.DateFormat
+import java.util.Date
+
+/**
+ * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was
+ * saved.
+ */
+data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) {
+ val subject: String
+
+ init {
+ val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
+ subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
+ }
+
+ companion object {
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 88bca951beb6..6b9332b39816 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -20,10 +20,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Notification
import android.content.Context
-import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Rect
-import android.net.Uri
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -35,7 +33,6 @@ import android.window.OnBackInvokedDispatcher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
-import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
@@ -45,7 +42,6 @@ import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
-import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -59,7 +55,7 @@ constructor(
private val viewModel: ScreenshotViewModel,
@Assisted private val context: Context,
@Assisted private val displayId: Int
-) : ScreenshotViewProxy, ScreenshotActionsProvider.ScreenshotActionsCallback {
+) : ScreenshotViewProxy {
override val view: ScreenshotShelfView =
LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
override val screenshotPreview: View
@@ -77,8 +73,6 @@ constructor(
override var isPendingSharedTransition = false
private val animationController = ScreenshotAnimationController(view)
- private var imageData: SavedImageData? = null
- private var runOnImageDataAcquired: ((SavedImageData) -> Unit)? = null
init {
ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
@@ -91,9 +85,7 @@ constructor(
override fun reset() {
animationController.cancel()
isPendingSharedTransition = false
- imageData = null
viewModel.reset()
- runOnImageDataAcquired = null
}
override fun updateInsets(insets: WindowInsets) {}
override fun updateOrientation(insets: WindowInsets) {}
@@ -104,12 +96,9 @@ constructor(
override fun addQuickShareChip(quickShareAction: Notification.Action) {}
- override fun setChipIntents(data: SavedImageData) {
- imageData = data
- runOnImageDataAcquired?.invoke(data)
- }
+ override fun setChipIntents(imageData: SavedImageData) {}
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }
// If we're already animating out, don't restart the animation
@@ -117,7 +106,7 @@ constructor(
debugLog(DEBUG_DISMISS) { "Already dismissing, ignoring duplicate command $event" }
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(it, 0, packageName) }
val animator = animationController.getExitAnimation()
animator.addListener(
object : AnimatorListenerAdapter() {
@@ -143,13 +132,18 @@ constructor(
newScreenshot: Bitmap,
screenshotTakenInPortrait: Boolean,
onTransitionPrepared: Runnable,
- ) {}
+ ) {
+ onTransitionPrepared.run()
+ }
override fun startLongScreenshotTransition(
transitionDestination: Rect,
onTransitionEnd: Runnable,
longScreenshot: ScrollCaptureController.LongScreenshot
- ) {}
+ ) {
+ onTransitionEnd.run()
+ callbacks?.onDismiss()
+ }
override fun restoreNonScrollingUi() {}
@@ -219,41 +213,4 @@ constructor(
interface Factory : ScreenshotViewProxy.Factory {
override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
}
-
- override fun setPreviewAction(overrideTransition: Boolean, retrieveIntent: (Uri) -> Intent) {
- viewModel.setPreviewAction {
- imageData?.let {
- val intent = retrieveIntent(it.uri)
- debugLog(DEBUG_ACTIONS) { "Preview tapped: $intent" }
- isPendingSharedTransition = true
- callbacks?.onAction(intent, it.owner, overrideTransition)
- }
- }
- }
-
- override fun addActions(actions: List<ScreenshotActionsProvider.ScreenshotAction>) {
- viewModel.addActions(
- actions.map { action ->
- ActionButtonViewModel(action.icon, action.text, action.description) {
- val actionRunnable =
- getActionRunnable(action.retrieveIntent, action.overrideTransition)
- imageData?.let { actionRunnable(it) }
- ?: run { runOnImageDataAcquired = actionRunnable }
- }
- }
- )
- }
-
- private fun getActionRunnable(
- retrieveIntent: (Uri) -> Intent,
- overrideTransition: Boolean
- ): (SavedImageData) -> Unit {
- val onClick: (SavedImageData) -> Unit = {
- val intent = retrieveIntent(it.uri)
- debugLog(DEBUG_ACTIONS) { "Action tapped: $intent" }
- isPendingSharedTransition = true
- callbacks!!.onAction(intent, it.owner, overrideTransition)
- }
- return onClick
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 65e845749f9e..59e38a836258 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -259,16 +259,8 @@ public class ScreenshotView extends FrameLayout implements
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "scroll chip tapped");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
- packageName);
- onClick.run();
- });
+ mScrollChip.setOnClickListener((v) -> onClick.run());
}
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 6be32a97f4e2..a4069d11f8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -46,7 +46,7 @@ interface ScreenshotViewProxy {
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun requestDismissal(event: ScreenshotEvent)
+ fun requestDismissal(event: ScreenshotEvent?)
fun showScrollChip(packageName: String, onClick: Runnable)
fun hideScrollChip()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
new file mode 100644
index 000000000000..a895b300b900
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.os.Process
+import android.os.SystemClock
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import javax.inject.Inject
+import kotlin.random.Random
+
+/**
+ * Handle requesting smart/quickshare actions from the provider and executing an action when the
+ * action futures complete.
+ */
+class SmartActionsProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val smartActions: ScreenshotNotificationSmartActionsProvider,
+) {
+ /**
+ * Requests quick share action for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param onAction callback to run when quick share action is returned
+ */
+ fun requestQuickShare(
+ data: ScreenshotData,
+ id: String,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare ->
+ onAction(quickShare)
+ }
+ }
+
+ /**
+ * Requests smart actions for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param result the data for the saved image
+ * @param onActions callback to run when actions are returned
+ */
+ fun requestSmartActions(
+ data: ScreenshotData,
+ id: String,
+ result: ScreenshotSavedResult,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestSmartActions(
+ id,
+ bitmap,
+ component,
+ data.getUserOrDefault(),
+ result.uri,
+ REGULAR_SMART_ACTIONS
+ ) { actions ->
+ onActions(actions)
+ }
+ }
+
+ /**
+ * Wraps the given quick share action in a broadcast intent.
+ *
+ * @param quickShare the quick share action to wrap
+ * @param uri the URI of the saved screenshot
+ * @param subject the subject/title for the screenshot
+ * @param id the request ID of the screenshot
+ * @return the pending intent with correct URI
+ */
+ fun wrapIntent(
+ quickShare: Notification.Action,
+ uri: Uri,
+ subject: String,
+ id: String
+ ): PendingIntent {
+ val wrappedIntent: Intent =
+ Intent(context, SmartActionsReceiver::class.java)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(
+ ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, subject)
+ )
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ val extras: Bundle = quickShare.extras
+ val actionType =
+ extras.getString(
+ ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+ ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE
+ )
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ wrappedIntent
+ .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(ScreenshotController.EXTRA_ID, id)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
+ return PendingIntent.getBroadcast(
+ context,
+ Random.nextInt(),
+ wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+
+ private fun createFillInIntent(uri: Uri, subject: String): Intent {
+ val fillIn = Intent()
+ fillIn.setType("image/png")
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri)
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, subject)
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ val clipData =
+ ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri))
+ fillIn.clipData = clipData
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ return fillIn
+ }
+
+ private fun requestQuickShareAction(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ timeoutMs: Long = 500,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) {
+ it.firstOrNull()?.let { action -> onAction(action) }
+ }
+ }
+
+ private fun requestSmartActions(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ uri: Uri?,
+ actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType,
+ timeoutMs: Long = 500,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val enabled = isSmartActionsEnabled(user)
+ debugLog(DEBUG_ACTIONS) {
+ ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " +
+ "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user")
+ }
+ if (!enabled) {
+ debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" }
+ onActions(listOf())
+ return
+ }
+ if (image.config != Bitmap.Config.HARDWARE) {
+ debugLog(DEBUG_ACTIONS) {
+ "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list."
+ }
+ onActions(listOf())
+ return
+ }
+ val smartActionsFuture: CompletableFuture<List<Notification.Action>>
+ val startTimeMs = SystemClock.uptimeMillis()
+ try {
+ smartActionsFuture =
+ smartActions.getActions(id, uri, image, component, actionType, user)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Failed to get future for screenshot notification smart actions."
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs
+ )
+ onActions(listOf())
+ return
+ }
+ try {
+ val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS) {
+ ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " +
+ "actionType=$actionType")
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs
+ )
+ onActions(actions)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType"
+ }
+ val status =
+ if (e is TimeoutException) {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ } else {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status,
+ waitTimeMs
+ )
+ onActions(listOf())
+ }
+ }
+
+ private fun notifyScreenshotOp(
+ screenshotId: String,
+ op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp,
+ status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus,
+ durationMs: Long
+ ) {
+ debugLog(DEBUG_ACTIONS) {
+ "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs"
+ }
+ try {
+ smartActions.notifyOp(screenshotId, op, status, durationMs)
+ } catch (e: Throwable) {
+ Log.e(TAG, "Error in notifyScreenshotOp: ", e)
+ }
+ }
+
+ private fun isSmartActionsEnabled(user: UserHandle): Boolean {
+ // Smart actions don't yet work for cross-user saves.
+ val savingToOtherUser = user !== Process.myUserHandle()
+ val actionsEnabled =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true
+ )
+ return !savingToOtherUser && actionsEnabled
+ }
+
+ companion object {
+ private const val TAG = "SmartActionsProvider"
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 92d3e550dc0f..ec7707c83980 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -92,14 +92,14 @@ constructor(
// Let's wait before logging "screenshot requested", as we should log the processed
// ScreenshotData.
val screenshotData =
- try {
- screenshotRequestProcessor.process(rawScreenshotData)
- } catch (e: RequestProcessorException) {
- Log.e(TAG, "Failed to process screenshot request!", e)
- logScreenshotRequested(rawScreenshotData)
- onFailedScreenshotRequest(rawScreenshotData, callback)
- return
- }
+ runCatching { screenshotRequestProcessor.process(rawScreenshotData) }
+ .onFailure {
+ Log.e(TAG, "Failed to process screenshot request!", it)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ }
+ .getOrNull()
+ ?: return
logScreenshotRequested(screenshotData)
Log.d(TAG, "Screenshot request: $screenshotData")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
new file mode 100644
index 000000000000..c380db0ca3a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.content.ComponentName
+import android.graphics.Rect
+
+/** A child task within a RootTaskInfo */
+data class ChildTaskModel(
+ /** The task identifier */
+ val id: Int,
+ /** The task name */
+ val name: String,
+ /** The location and size of the task */
+ val bounds: Rect,
+ /** The user which created the task. */
+ val userId: Int,
+) {
+ val componentName: ComponentName?
+ get() = ComponentName.unflattenFromString(name)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 837a661230cb..2048b7c0c142 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -24,6 +24,6 @@ data class DisplayContentModel(
val displayId: Int,
/** Information about the current System UI state which can affect capture. */
val systemUiState: SystemUiState,
- /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+ /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
val rootTasks: List<RootTaskInfo>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
index 9c81b322a2b7..48e813d89af7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -18,7 +18,7 @@ package com.android.systemui.screenshot.data.repository
import com.android.systemui.screenshot.data.model.DisplayContentModel
/** Provides information about tasks related to a display. */
-interface DisplayContentRepository {
+fun interface DisplayContentRepository {
/** Provides information about the tasks and content presented on a given display. */
suspend fun getDisplayContent(displayId: Int): DisplayContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
new file mode 100644
index 000000000000..5e2b57651de7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class CaptureParameters(
+ /** How should the content be captured? */
+ val type: CaptureType,
+ /** The focused or top component at the time of the screenshot. */
+ val component: ComponentName?,
+ /** Which user should receive the screenshot file? */
+ val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
new file mode 100644
index 000000000000..4a88180d8f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -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 com.android.systemui.screenshot.policy
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Contains logic to determine when and how an adjust to screenshot behavior applies. */
+fun interface CapturePolicy {
+ /**
+ * Test the policy against the current display task state. If the policy applies, Returns a
+ * non-null [CaptureParameters] describing how the screenshot request should be augmented.
+ */
+ suspend fun apply(content: DisplayContentModel): CaptureParameters?
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
new file mode 100644
index 000000000000..6ca2e9d6d5e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.graphics.Rect
+
+/** What to capture */
+sealed interface CaptureType {
+ /** Capture the entire screen contents. */
+ class FullScreen(val displayId: Int) : CaptureType
+
+ /** Capture the contents of the task only. */
+ class IsolatedTask(
+ val taskId: Int,
+ val taskBounds: Rect?,
+ ) : CaptureType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
new file mode 100644
index 000000000000..2c0a0dbf8ea9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.ScreenshotData
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+private const val TAG = "PolicyRequestProcessor"
+
+/** A [ScreenshotRequestProcessor] which supports general policy rule matching. */
+class PolicyRequestProcessor(
+ @Background private val background: CoroutineDispatcher,
+ private val capture: ImageCapture,
+ private val displayTasks: DisplayContentRepository,
+ private val policies: List<CapturePolicy>,
+) : ScreenshotRequestProcessor {
+ override suspend fun process(original: ScreenshotData): ScreenshotData {
+ if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ // The request contains an already captured screenshot, accept it as is.
+ Log.i(TAG, "Screenshot bitmap provided. No modifications applied.")
+ return original
+ }
+
+ val tasks = displayTasks.getDisplayContent(original.displayId)
+
+ // If policies yield explicit modifications, apply them and return the result
+ Log.i(TAG, "Applying policy checks....")
+ policies
+ .firstNotNullOfOrNull { policy -> policy.apply(tasks) }
+ ?.let {
+ Log.i(TAG, "Modifying screenshot: $it")
+ return apply(it, original)
+ }
+
+ // Otherwise capture normally, filling in additional information as needed.
+ return replaceWithScreenshot(
+ original = original,
+ componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity,
+ owner = original.userHandle,
+ displayId = original.displayId
+ )
+ }
+
+ /** Produce a new [ScreenshotData] using [CaptureParameters] */
+ suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData {
+ // Update and apply bitmap capture depending on the parameters.
+ val updated =
+ when (val type = updates.type) {
+ is IsolatedTask ->
+ replaceWithTaskSnapshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.taskId,
+ type.taskBounds
+ )
+ is FullScreen ->
+ replaceWithScreenshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.displayId
+ )
+ }
+ return updated
+ }
+
+ suspend fun replaceWithTaskSnapshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle,
+ taskId: Int,
+ taskBounds: Rect?,
+ ): ScreenshotData {
+ val taskSnapshot = capture.captureTask(taskId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ bitmap = taskSnapshot,
+ userHandle = owner,
+ taskId = taskId,
+ topComponent = componentName,
+ screenBounds = taskBounds
+ )
+ }
+
+ suspend fun replaceWithScreenshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle?,
+ displayId: Int,
+ ): ScreenshotData {
+ val screenshot = captureDisplay(displayId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_FULLSCREEN,
+ bitmap = screenshot,
+ userHandle = owner,
+ topComponent = componentName,
+ screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+ )
+ }
+
+ /** TODO: Move to ImageCapture (existing function is non-suspending) */
+ private suspend fun captureDisplay(displayId: Int): Bitmap? {
+ return withContext(background) { capture.captureDisplay(displayId) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
new file mode 100644
index 000000000000..221e64782894
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import javax.inject.Inject
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.firstOrNull
+
+/**
+ * Condition: When any visible task belongs to a private user.
+ *
+ * Parameters: Capture the whole screen, owned by the private user.
+ */
+class PrivateProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+ override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+ // Find the first visible rootTaskInfo with a child task owned by a private user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible }
+ .firstNotNullOfOrNull { root ->
+ root
+ .childTasksTopDown()
+ .firstOrNull {
+ profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+ }
+ ?.let { root to it }
+ }
+ ?: return null
+
+ // If matched, return parameters needed to modify the request.
+ return CaptureParameters(
+ type = FullScreen(content.displayId),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
new file mode 100644
index 000000000000..d2f4d9e039f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.data.model.ChildTaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+
+internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> {
+ return ((numActivities - 1) downTo 0).asFlow().map { index ->
+ ChildTaskModel(
+ childTaskIds[index],
+ childTaskNames[index],
+ childTaskBounds[index],
+ childTaskUserIds[index]
+ )
+ }
+}
+
+internal suspend fun RootTaskInfo.firstChildTaskOrNull(
+ filter: suspend (Int) -> Boolean
+): Pair<RootTaskInfo, Int>? {
+ // Child tasks are provided in bottom-up order
+ // Filtering is done top-down, so iterate backwards here.
+ for (index in numActivities - 1 downTo 0) {
+ if (filter(index)) {
+ return (this to index)
+ }
+ }
+ return null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index bc71ab71b626..63d15087d12c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -16,7 +16,9 @@
package com.android.systemui.screenshot.policy
+import com.android.systemui.Flags.screenshotPrivateProfile
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
import com.android.systemui.screenshot.RequestProcessor
import com.android.systemui.screenshot.ScreenshotPolicy
@@ -29,6 +31,7 @@ import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
@Module
interface ScreenshotPolicyModule {
@@ -37,18 +40,42 @@ interface ScreenshotPolicyModule {
@SysUISingleton
fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
+ @Binds
+ @SysUISingleton
+ fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
+
companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindCapturePolicyList(
+ privateProfilePolicy: PrivateProfilePolicy,
+ workProfilePolicy: WorkProfilePolicy,
+ ): List<CapturePolicy> {
+ // In order of priority. The first matching policy applies.
+ return listOf(workProfilePolicy, privateProfilePolicy)
+ }
+
+ @JvmStatic
@Provides
@SysUISingleton
fun bindScreenshotRequestProcessor(
+ @Background background: CoroutineDispatcher,
imageCapture: ImageCapture,
policyProvider: Provider<ScreenshotPolicy>,
+ displayContentRepoProvider: Provider<DisplayContentRepository>,
+ policyListProvider: Provider<List<CapturePolicy>>,
): ScreenshotRequestProcessor {
- return RequestProcessor(imageCapture, policyProvider.get())
+ return if (screenshotPrivateProfile()) {
+ PolicyRequestProcessor(
+ background,
+ imageCapture,
+ displayContentRepoProvider.get(),
+ policyListProvider.get()
+ )
+ } else {
+ RequestProcessor(imageCapture, policyProvider.get())
+ }
}
}
-
- @Binds
- @SysUISingleton
- fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
new file mode 100644
index 000000000000..d6b5d6dfda25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.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.screenshot.policy
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+
+/**
+ * Condition: When the top visible task (excluding PIP mode) belongs to a work user.
+ *
+ * Parameters: Capture only the foreground task, owned by the work user.
+ */
+class WorkProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+ override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+ // Find the first non PiP rootTask with a top child task owned by a work user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .map { it to it.childTasksTopDown().first() }
+ .firstOrNull { (_, child) ->
+ profileTypes.getProfileType(child.userId) == ProfileType.WORK
+ }
+ ?: return null
+
+ // If matched, return parameters needed to modify the request.
+ return CaptureParameters(
+ type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 1e1a577ebd4a..706ac9c46be1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -335,8 +335,8 @@ public class LongScreenshotActivity extends Activity {
// TODO: Fix transition for work profile. Omitting it in the meantime.
mActionExecutor.launchIntentAsync(
ActionIntentCreator.INSTANCE.createEdit(uri, this),
- null,
- mScreenshotUserHandle, false);
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
} else {
String editorPackage = getString(R.string.config_screenshotEditor);
Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -363,7 +363,8 @@ public class LongScreenshotActivity extends Activity {
private void doShare(Uri uri) {
Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
- mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
+ mActionExecutor.launchIntentAsync(shareIntent, mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
}
private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
new file mode 100644
index 000000000000..6c4ee3e9e89b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.scroll
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.Log
+import android.view.ScrollCaptureResponse
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import javax.inject.Inject
+
+class ScrollCaptureExecutor
+@Inject
+constructor(
+ activityManager: ActivityManager,
+ private val scrollCaptureClient: ScrollCaptureClient,
+ private val scrollCaptureController: ScrollCaptureController,
+ private val longScreenshotHolder: LongScreenshotData,
+ @Main private val mainExecutor: Executor
+) {
+ private val isLowRamDevice = activityManager.isLowRamDevice
+ private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null
+ private var lastScrollCaptureResponse: ScrollCaptureResponse? = null
+ private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null
+
+ fun requestScrollCapture(
+ displayId: Int,
+ token: IBinder,
+ callback: (ScrollCaptureResponse) -> Unit
+ ) {
+ if (!allowLongScreenshots()) {
+ Log.d(TAG, "Long screenshots not supported on this device")
+ return
+ }
+ scrollCaptureClient.setHostWindowToken(token)
+ lastScrollCaptureRequest?.cancel(true)
+ val scrollRequest =
+ scrollCaptureClient.request(displayId).apply {
+ addListener(
+ { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } },
+ mainExecutor
+ )
+ }
+ lastScrollCaptureRequest = scrollRequest
+ }
+
+ fun interface ScrollTransitionReady {
+ fun onTransitionReady(
+ destRect: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: LongScreenshot
+ )
+ }
+
+ fun executeBatchScrollCapture(
+ response: ScrollCaptureResponse,
+ onCaptureComplete: Runnable,
+ onFailure: Runnable,
+ transition: ScrollTransitionReady,
+ ) {
+ // Clear the reference to prevent close() on reset
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ longScreenshotFuture =
+ scrollCaptureController.run(response).apply {
+ addListener(
+ {
+ getLongScreenshotChecked(this, onFailure)?.let {
+ longScreenshotHolder.setLongScreenshot(it)
+ longScreenshotHolder.setTransitionDestinationCallback {
+ destinationRect: Rect,
+ onTransitionEnd: Runnable ->
+ transition.onTransitionReady(destinationRect, onTransitionEnd, it)
+ }
+ onCaptureComplete.run()
+ }
+ },
+ mainExecutor
+ )
+ }
+ }
+
+ fun close() {
+ lastScrollCaptureRequest?.cancel(true)
+ lastScrollCaptureRequest = null
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ }
+
+ private fun getLongScreenshotChecked(
+ future: ListenableFuture<LongScreenshot>,
+ onFailure: Runnable
+ ): LongScreenshot? {
+ var longScreenshot: LongScreenshot? = null
+ runCatching { longScreenshot = future.get() }
+ .onFailure {
+ Log.e(TAG, "Caught exception", it)
+ onFailure.run()
+ return null
+ }
+ if (longScreenshot?.height != 0) {
+ return longScreenshot
+ }
+ onFailure.run()
+ return null
+ }
+
+ private fun onScrollCaptureResponseReady(
+ responseFuture: Future<ScrollCaptureResponse>
+ ): ScrollCaptureResponse? {
+ try {
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ if (responseFuture.isCancelled) {
+ return null
+ }
+ val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this }
+ if (!captureResponse.isConnected) {
+ // No connection means that the target window wasn't found
+ // or that it cannot support scroll capture.
+ Log.d(
+ TAG,
+ "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]"
+ )
+ return null
+ }
+ Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]")
+ return captureResponse
+ } catch (e: InterruptedException) {
+ Log.e(TAG, "requestScrollCapture interrupted", e)
+ } catch (e: ExecutionException) {
+ Log.e(TAG, "requestScrollCapture failed", e)
+ }
+ return null
+ }
+
+ private fun allowLongScreenshots(): Boolean {
+ return !isLowRamDevice
+ }
+
+ private companion object {
+ private const val TAG = "ScrollCaptureExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index c7fe3f608a2f..a6374ae3304d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -36,6 +36,7 @@ object ActionButtonViewBinder {
} else {
view.setOnClickListener(null)
}
+ view.tag = viewModel.id
view.contentDescription = viewModel.description
view.visibility = View.VISIBLE
view.alpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d8782009e24b..32e9296107a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -60,7 +60,7 @@ object ScreenshotShelfViewBinder {
}
launch {
viewModel.previewAction.collect { onClick ->
- previewView.setOnClickListener { onClick?.run() }
+ previewView.setOnClickListener { onClick?.invoke() }
}
}
launch {
@@ -70,21 +70,36 @@ object ScreenshotShelfViewBinder {
.requireViewById<View>(R.id.actions_container_background)
.visibility = View.VISIBLE
}
- val viewPool = actionsContainer.children.toList()
- actionsContainer.removeAllViews()
- val actionButtons =
- List(actions.size) {
- viewPool.getOrElse(it) {
+
+ // Remove any buttons not in the new list, then do another pass to add
+ // any new actions and update any that are already there.
+ // This assumes that actions can never change order and that each action
+ // ID is unique.
+ val newIds = actions.map { it.id }
+
+ for (view in actionsContainer.children.toList()) {
+ if (view.tag !in newIds) {
+ actionsContainer.removeView(view)
+ }
+ }
+
+ for ((index, action) in actions.withIndex()) {
+ val currentView: View? = actionsContainer.getChildAt(index)
+ if (action.id == currentView?.tag) {
+ // Same ID, update the display
+ ActionButtonViewBinder.bind(currentView, action)
+ } else {
+ // Different ID. Removals have already happened so this must
+ // mean that the new action must be inserted here.
+ val actionButton =
layoutInflater.inflate(
R.layout.overlay_action_chip,
actionsContainer,
false
)
- }
+ actionsContainer.addView(actionButton, index)
+ ActionButtonViewBinder.bind(actionButton, action)
}
- actionButtons.zip(actions).forEach {
- actionsContainer.addView(it.first)
- ActionButtonViewBinder.bind(it.first, it.second)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index 05bfed159527..97b24c1b7df7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -22,5 +22,13 @@ data class ActionButtonViewModel(
val icon: Drawable?,
val name: CharSequence?,
val description: CharSequence,
- val onClicked: (() -> Unit)?
-)
+ val onClicked: (() -> Unit)?,
+) {
+ val id: Int = getId()
+
+ companion object {
+ private var nextId = 0
+
+ private fun getId() = nextId.also { nextId += 1 }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index dc61d1e9c37b..ddfa69b687eb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.StateFlow
class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
private val _preview = MutableStateFlow<Bitmap?>(null)
val preview: StateFlow<Bitmap?> = _preview
- private val _previewAction = MutableStateFlow<Runnable?>(null)
- val previewAction: StateFlow<Runnable?> = _previewAction
+ private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
+ val previewAction: StateFlow<(() -> Unit)?> = _previewAction
private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
val actions: StateFlow<List<ActionButtonViewModel>> = _actions
val showDismissButton: Boolean
@@ -35,8 +35,14 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager
_preview.value = bitmap
}
- fun setPreviewAction(runnable: Runnable) {
- _previewAction.value = runnable
+ fun setPreviewAction(onClick: () -> Unit) {
+ _previewAction.value = onClick
+ }
+
+ fun addAction(action: ActionButtonViewModel) {
+ val actionList = _actions.value.toMutableList()
+ actionList.add(action)
+ _actions.value = actionList
}
fun addActions(actions: List<ActionButtonViewModel>) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 92d6ec97ad83..8397d9f1e7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -52,7 +52,6 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.settings.SecureSettings;
import dagger.assisted.Assisted;
@@ -107,7 +106,7 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig
private ValueAnimator mSliderAnimator;
@Override
- public void setMirror(BrightnessMirrorController controller) {
+ public void setMirror(@Nullable MirrorController controller) {
mControl.setMirrorControllerAndMirror(controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
index 701d814a843b..073279be5af2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -16,12 +16,9 @@
package com.android.systemui.settings.brightness
-import com.android.systemui.statusbar.policy.BrightnessMirrorController
-import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
-
class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) {
- var mirrorController: BrightnessMirrorController? = null
+ var mirrorController: MirrorController? = null
private set
var brightnessController: MirroredBrightnessController = brightnessController
@@ -30,7 +27,8 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
updateBrightnessMirror()
}
- private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+ private val brightnessMirrorListener =
+ MirrorController.BrightnessMirrorListener { updateBrightnessMirror() }
fun onQsPanelAttached() {
mirrorController?.addCallback(brightnessMirrorListener)
@@ -40,7 +38,7 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
mirrorController?.removeCallback(brightnessMirrorListener)
}
- fun setController(controller: BrightnessMirrorController?) {
+ fun setController(controller: MirrorController?) {
mirrorController?.removeCallback(brightnessMirrorListener)
mirrorController = controller
mirrorController?.addCallback(brightnessMirrorListener)
@@ -48,6 +46,6 @@ class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController
}
private fun updateBrightnessMirror() {
- mirrorController?.let { brightnessController.setMirror(it) }
+ brightnessController.setMirror(mirrorController)
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 539b0c2dd599..b425fb997d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -57,8 +57,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
ToggleSlider {
private Listener mListener;
+ @Nullable
private ToggleSlider mMirror;
- private BrightnessMirrorController mMirrorController;
+ @Nullable
+ private MirrorController mMirrorController;
private boolean mTracking;
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
@@ -108,6 +110,9 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
protected void onViewAttached() {
mView.setOnSeekBarChangeListener(mSeekListener);
mView.setOnInterceptListener(mOnInterceptListener);
+ if (mMirror != null) {
+ mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
+ }
}
@Override
@@ -129,7 +134,10 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
private boolean copyEventToMirror(MotionEvent ev) {
MotionEvent copy = ev.copy();
- boolean out = mMirror.mirrorTouchEvent(copy);
+ boolean out = false;
+ if (mMirror != null) {
+ out = mMirror.mirrorTouchEvent(copy);
+ }
copy.recycle();
return out;
}
@@ -166,9 +174,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
* @param c
*/
@Override
- public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
+ public void setMirrorControllerAndMirror(@Nullable MirrorController c) {
mMirrorController = c;
- setMirror(c.getToggleSlider());
+ if (c != null) {
+ setMirror(c.getToggleSlider());
+ } else {
+ setMirror(null);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
new file mode 100644
index 000000000000..6a9af26ba4a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.view.View
+import com.android.systemui.settings.brightness.MirrorController.BrightnessMirrorListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface MirrorController : CallbackController<BrightnessMirrorListener> {
+
+ /**
+ * Get the [ToggleSlider] currently associated with this controller, or `null` if none currently
+ */
+ fun getToggleSlider(): ToggleSlider?
+
+ /**
+ * Indicate to this controller that the user is dragging on the brightness view and the mirror
+ * should show
+ */
+ fun showMirror()
+
+ /**
+ * Indicate to this controller that the user has stopped dragging on the brightness view and the
+ * mirror should hide
+ */
+ fun hideMirror()
+
+ /**
+ * Set the location and size of the current brightness [view] in QS so it can be properly
+ * adapted to show the mirror in the same location and with the same size.
+ */
+ fun setLocationAndSize(view: View)
+
+ fun interface BrightnessMirrorListener {
+ fun onBrightnessMirrorReinflated(brightnessMirror: View?)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
index 8d857dec2108..b1a532baa710 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -22,5 +22,5 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController
* Indicates controller that has brightness slider and uses [BrightnessMirrorController]
*/
interface MirroredBrightnessController {
- fun setMirror(controller: BrightnessMirrorController)
+ fun setMirror(controller: MirrorController?)
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 648e33b1d228..24bc67047a47 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -19,7 +19,6 @@ package com.android.systemui.settings.brightness;
import android.view.MotionEvent;
import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
public interface ToggleSlider {
interface Listener {
@@ -27,7 +26,7 @@ public interface ToggleSlider {
}
void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin);
- void setMirrorControllerAndMirror(BrightnessMirrorController c);
+ void setMirrorControllerAndMirror(MirrorController c);
boolean mirrorTouchEvent(MotionEvent ev);
void setOnChangedListener(Listener l);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
new file mode 100644
index 000000000000..a0c9be46b6e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorShowingRepository @Inject constructor() {
+ private val _isShowing = MutableStateFlow(false)
+ val isShowing = _isShowing.asStateFlow()
+
+ fun setMirrorShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
new file mode 100644
index 000000000000..ef6e72f76ae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessMirrorShowingInteractor
+@Inject
+constructor(
+ private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository,
+) {
+ val isShowing = brightnessMirrorShowingRepository.isShowing
+
+ fun setMirrorShowing(showing: Boolean) {
+ brightnessMirrorShowingRepository.setMirrorShowing(showing)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
new file mode 100644
index 000000000000..468a87325507
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+
+object BrightnessMirrorInflater {
+
+ fun inflate(
+ context: Context,
+ sliderControllerFactory: BrightnessSliderController.Factory,
+ ): Pair<View, BrightnessSliderController> {
+ val frame =
+ (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null)
+ as ViewGroup)
+ .apply { isVisible = true }
+ val sliderController = sliderControllerFactory.create(context, frame)
+ sliderController.init()
+ frame.addView(
+ sliderController.rootView,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ return frame to sliderController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
new file mode 100644
index 000000000000..2651a994bb55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewModel
+
+import android.content.res.Resources
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.settings.brightness.ToggleSlider
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorViewModel
+@Inject
+constructor(
+ private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+ @Main private val resources: Resources,
+ val sliderControllerFactory: BrightnessSliderController.Factory,
+) : MirrorController {
+
+ private val tempPosition = IntArray(2)
+
+ private var _toggleSlider: BrightnessSliderController? = null
+
+ val isShowing = brightnessMirrorShowingInteractor.isShowing
+
+ private val _locationAndSize: MutableStateFlow<LocationAndSize> =
+ MutableStateFlow(LocationAndSize())
+ val locationAndSize = _locationAndSize.asStateFlow()
+
+ override fun getToggleSlider(): ToggleSlider? {
+ return _toggleSlider
+ }
+
+ fun setToggleSlider(toggleSlider: BrightnessSliderController) {
+ _toggleSlider = toggleSlider
+ }
+
+ override fun showMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(true)
+ }
+
+ override fun hideMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(false)
+ }
+
+ override fun setLocationAndSize(view: View) {
+ view.getLocationInWindow(tempPosition)
+ val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+ _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding)
+ // Account for desired padding
+ _locationAndSize.value =
+ LocationAndSize(
+ yOffset = tempPosition[1] - padding,
+ width = view.measuredWidth + 2 * padding,
+ height = view.measuredHeight + 2 * padding,
+ )
+ }
+
+ // Callbacks are used for indicating reinflation when the config changes in some ways (like
+ // density). However, we don't need that as we recompose the view anyway
+ override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {}
+
+ override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+}
+
+data class LocationAndSize(
+ val yOffset: Int = 0,
+ val width: Int = 0,
+ val height: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index c42fdf8e7b93..b24edd9beece 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -25,6 +25,7 @@ import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import com.android.keyguard.LockIconViewController;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import java.util.HashSet;
@@ -80,12 +81,14 @@ public class DebugDrawable extends Drawable {
mNotificationPanelViewController.getClockPositionResult()
.stackScrollerPadding),
Color.YELLOW, "calculatePanelHeightShade()");
- drawDebugInfo(canvas,
- (int) mQsController.calculateNotificationsTopPadding(
- mNotificationPanelViewController.isExpandingOrCollapsing(),
- mNotificationPanelViewController.getKeyguardNotificationStaticPadding(),
- mNotificationPanelViewController.getExpandedFraction()),
- Color.MAGENTA, "calculateNotificationsTopPadding()");
+ if (!SceneContainerFlag.isEnabled()) {
+ drawDebugInfo(canvas,
+ (int) mQsController.calculateNotificationsTopPadding(
+ mNotificationPanelViewController.isExpandingOrCollapsing(),
+ mNotificationPanelViewController.getKeyguardNotificationStaticPadding(),
+ mNotificationPanelViewController.getExpandedFraction()),
+ Color.MAGENTA, "calculateNotificationsTopPadding()");
+ }
drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
Color.GRAY, "mClockPositionResult.clockY");
drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 343f37744ce9..4660831b77af 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1588,7 +1588,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* @param forceClockUpdate Should the clock be updated even when not on keyguard
*/
private void positionClockAndNotifications(boolean forceClockUpdate) {
- boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
+ boolean animate = !SceneContainerFlag.isEnabled()
+ && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
boolean onKeyguard = isKeyguardShowing();
@@ -1675,7 +1676,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mClockPositionResult.clockX, mClockPositionResult.clockY);
}
- boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
+ boolean animate = !SceneContainerFlag.isEnabled()
+ && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
if (!MigrateClocksToBlueprint.isEnabled()) {
@@ -2483,6 +2485,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
/** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
int getKeyguardNotificationStaticPadding() {
+ SceneContainerFlag.assertInLegacyMode();
if (!isKeyguardShowing()) {
return 0;
}
@@ -2524,12 +2527,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
void requestScrollerTopPaddingUpdate(boolean animate) {
- float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
- getKeyguardNotificationStaticPadding(), mExpandedFraction);
- if (MigrateClocksToBlueprint.isEnabled()) {
- mSharedNotificationContainerInteractor.setTopPosition(padding);
- } else {
- mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
+ if (!SceneContainerFlag.isEnabled()) {
+ float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
+ getKeyguardNotificationStaticPadding(), mExpandedFraction);
+ if (MigrateClocksToBlueprint.isEnabled()) {
+ mSharedNotificationContainerInteractor.setTopPosition(padding);
+ } else {
+ mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
+ }
}
if (isKeyguardShowing()
@@ -3174,6 +3179,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
notifyExpandingFinished();
}
+ // TODO(b/332732878): replace this call when scene container is enabled
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -3963,7 +3969,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mShadeRepository.setLegacyShadeExpansion(mExpandedFraction);
mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
mExpansionDragDownAmountPx = h;
- mAmbientState.setExpansionFraction(mExpandedFraction);
+ if (!SceneContainerFlag.isEnabled()) {
+ mAmbientState.setExpansionFraction(mExpandedFraction);
+ }
onHeightUpdated(mExpandedHeight);
updateExpansionAndVisibility();
});
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f7fed537a167..fb32b9fce909 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -18,7 +18,6 @@ package com.android.systemui.shade;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
@@ -290,12 +289,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
- // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
- // window manager which disables the transient show behavior.
- // TODO: Clean this up once that behavior moves into the Shell.
- mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
- mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-
if (mSceneContainerFlags.isEnabled()) {
// This prevents the appearance and disappearance of the software keyboard (also known
// as the "IME") from scrolling/panning the window to make room for the keyboard.
@@ -307,6 +300,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mWindowManager.addView(mWindowRootView, mLp);
+ // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
+ // window manager which disables the transient show behavior.
+ // TODO: Clean this up once that behavior moves into the Shell.
+ if (mWindowRootView.getWindowInsetsController() != null) {
+ mWindowRootView.getWindowInsetsController().setSystemBarsBehavior(
+ BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+ }
+
mLpChanged.copyFrom(mLp);
onThemeChanged();
@@ -416,6 +417,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
}
+
+ if (state.bouncerShowing) {
+ mLpChanged.inputFeatures |= LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
+ } else {
+ mLpChanged.inputFeatures &= ~LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
+ }
}
protected boolean isDebuggable() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index f9b4e671f373..903af61f2202 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -81,6 +81,8 @@ public class NotificationShadeWindowView extends WindowRootView {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ mInteractionEventHandler.collectKeyEvent(event);
+
if (mInteractionEventHandler.interceptMediaKey(event)) {
return true;
}
@@ -301,6 +303,11 @@ public class NotificationShadeWindowView extends WindowRootView {
boolean dispatchKeyEvent(KeyEvent event);
boolean dispatchKeyEventPreIme(KeyEvent event);
+
+ /**
+ * Collects the KeyEvent without intercepting it
+ */
+ void collectKeyEvent(KeyEvent event);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 324dfdf32a3f..6ac81d226eee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -563,6 +563,11 @@ public class NotificationShadeWindowViewController implements Dumpable {
public boolean dispatchKeyEvent(KeyEvent event) {
return mSysUIKeyEventHandler.dispatchKeyEvent(event);
}
+
+ @Override
+ public void collectKeyEvent(KeyEvent event) {
+ mFalsingCollector.onKeyEvent(event);
+ }
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 35b40592a47e..2507507ce22e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -1371,6 +1371,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
@Override
public float calculateNotificationsTopPadding(boolean isShadeExpanding,
int keyguardNotificationStaticPadding, float expandedFraction) {
+ SceneContainerFlag.assertInLegacyMode();
float topPadding;
boolean keyguardShowing = mBarState == KEYGUARD;
if (mSplitShadeEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
index b8250cc284bd..34629934e467 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import javax.inject.Inject
@@ -28,7 +27,6 @@ class QuickSettingsControllerSceneImpl
constructor(
private val shadeInteractor: ShadeInteractor,
private val qsSceneAdapter: QSSceneAdapter,
- private val qsContainerController: QSContainerController,
) : QuickSettingsController {
override val expanded: Boolean
@@ -43,7 +41,7 @@ constructor(
}
override fun closeQsCustomizer() {
- qsContainerController.setCustomizerShowing(false)
+ qsSceneAdapter.requestCloseCustomizer()
}
@Deprecated("specific to legacy split shade")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index c5e07e818b6b..ebebbe65d54b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -61,6 +62,7 @@ constructor(
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val notificationStackScrollLayout: NotificationStackScrollLayout,
@ShadeTouchLog private val touchLog: LogBuffer,
private val vibratorHelper: VibratorHelper,
@@ -148,7 +150,11 @@ constructor(
}
private fun getCollapseDestinationScene(): SceneKey {
- return if (deviceEntryInteractor.isDeviceEntered.value) {
+ // Always check whether device is unlocked before transitioning to gone scene.
+ return if (
+ deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked &&
+ deviceEntryInteractor.isDeviceEntered.value
+ ) {
Scenes.Gone
} else {
Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
index adb29287e40e..ec4018c7d238 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -46,7 +46,7 @@ class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewController
}
override fun setTouchAndAnimationDisabled(disabled: Boolean) {
- // TODO(b/322197941): determine if still needed
+ // TODO(b/332732878): determine if still needed
}
override fun setWillPlayDelayedDozeAmountAnimation(willPlay: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 2cb9f9acca26..f5dd5e465505 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -22,6 +22,7 @@ import android.os.Handler
import android.view.LayoutInflater
import android.view.ViewStub
import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.compose.animation.scene.SceneKey
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -43,6 +44,7 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -51,6 +53,7 @@ import com.android.systemui.statusbar.phone.TapAgainView
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.tuner.TunerService
+import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -59,6 +62,14 @@ import javax.inject.Provider
/** Module for providing views related to the shade. */
@Module
abstract class ShadeViewProviderModule {
+
+ @Binds
+ @SysUISingleton
+ // TODO(b/277762009): Only allow this view's binder to inject the view.
+ abstract fun bindsNotificationScrollView(
+ notificationStackScrollLayout: NotificationStackScrollLayout
+ ): NotificationScrollView
+
companion object {
const val SHADE_HEADER = "large_screen_shade_header"
@@ -76,6 +87,7 @@ abstract class ShadeViewProviderModule {
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
): WindowRootView {
return if (sceneContainerFlags.isEnabled()) {
+ checkNoSceneDuplicates(scenesProvider.get())
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
sceneWindowRootView.init(
@@ -271,5 +283,21 @@ abstract class ShadeViewProviderModule {
): StatusIconContainer {
return header.requireViewById(R.id.statusIcons)
}
+
+ private fun checkNoSceneDuplicates(scenes: Set<Scene>) {
+ val keys = mutableSetOf<SceneKey>()
+ val duplicates = mutableSetOf<SceneKey>()
+ scenes
+ .map { it.key }
+ .forEach { sceneKey ->
+ if (keys.contains(sceneKey)) {
+ duplicates.add(sceneKey)
+ } else {
+ keys.add(sceneKey)
+ }
+ }
+
+ check(duplicates.isEmpty()) { "Duplicate scenes detected: $duplicates" }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 3349345b1391..c4293291de51 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -345,9 +345,7 @@ public class ShadeCarrierGroupController {
}
}
- if (mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) {
- Log.d(TAG, "ignoring old pipeline callback because new mobile icon is enabled");
- } else {
+ if (!mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()) {
for (int i = 0; i < SIM_SLOTS; i++) {
mCarrierGroups[i].updateState(mInfos[i], singleCarrier);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9362cd058929..980f665ae61f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -57,6 +58,7 @@ constructor(
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val mediaDataManager: MediaDataManager,
shadeInteractor: ShadeInteractor,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
index 5e57f1d1a11f..8eed0975579d 100644
--- a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
@@ -27,4 +27,4 @@ import kotlin.reflect.KClass
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
-annotation class Dependencies(vararg val value: KClass<out CoreStartable> = [])
+annotation class Dependencies(vararg val value: KClass<*> = [])
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index d6858cad6d0b..78e108d444d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Pair;
@@ -57,6 +58,7 @@ import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
@@ -104,6 +106,7 @@ public final class KeyboardShortcutListSearch {
private WindowManager mWindowManager;
private EditText mSearchEditText;
+ private ImageButton mEditTextCancel;
private String mQueryString;
private int mCurrentCategoryIndex = 0;
private Map<Integer, Boolean> mKeySearchResultMap = new HashMap<>();
@@ -143,7 +146,7 @@ public final class KeyboardShortcutListSearch {
@VisibleForTesting
KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
this.mContext = new ContextThemeWrapper(
- context, android.R.style.Theme_DeviceDefault_Settings);
+ context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
if (windowManager != null) {
this.mWindowManager = windowManager;
@@ -853,13 +856,14 @@ public final class KeyboardShortcutListSearch {
List<List<KeyboardShortcutMultiMappingGroup>> keyboardShortcutMultiMappingGroupList) {
mQueryString = null;
LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
- mKeyboardShortcutsBottomSheetDialog =
- new BottomSheetDialog(mContext);
+ mKeyboardShortcutsBottomSheetDialog = new BottomSheetDialog(mContext);
final View keyboardShortcutsView = inflater.inflate(
R.layout.keyboard_shortcuts_search_view, null);
LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById(
R.id.keyboard_shortcuts_container);
mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
+ Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
+ setWindowProperties(keyboardShortcutsWindow);
mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
setButtonsDefaultStatus(keyboardShortcutsView);
populateCurrentAppButton();
@@ -874,25 +878,11 @@ public final class KeyboardShortcutListSearch {
}
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+ behavior.setDraggable(true);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setSkipCollapsed(true);
- behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
- @Override
- public void onStateChanged(@NonNull View bottomSheet, int newState) {
- if (newState == BottomSheetBehavior.STATE_DRAGGING) {
- behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
- }
- }
- @Override
- public void onSlide(@NonNull View bottomSheet, float slideOffset) {
- // Do nothing.
- }
- });
- mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
- Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
- keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
synchronized (sLock) {
// show KeyboardShortcutsBottomSheetDialog only if it has not been dismissed already
if (sInstance != null) {
@@ -908,6 +898,8 @@ public final class KeyboardShortcutListSearch {
}
}
mSearchEditText = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search);
+ mEditTextCancel = keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_search_cancel);
mSearchEditText.addTextChangedListener(
new TextWatcher() {
@Override
@@ -921,6 +913,8 @@ public final class KeyboardShortcutListSearch {
shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
R.string.keyboard_shortcut_a11y_show_search_results));
}
+ mEditTextCancel.setVisibility(
+ TextUtils.isEmpty(mQueryString) ? View.GONE : View.VISIBLE);
}
@Override
@@ -933,9 +927,28 @@ public final class KeyboardShortcutListSearch {
// Do nothing.
}
});
- ImageButton editTextCancel = keyboardShortcutsView.findViewById(
- R.id.keyboard_shortcuts_search_cancel);
- editTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+
+ mEditTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+ }
+
+ private static void setWindowProperties(Window keyboardShortcutsWindow) {
+ keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.copyFrom(keyboardShortcutsWindow.getAttributes());
+ // Allows the bottom sheet dialog to render all the way to the bottom of the screen,
+ // behind the gesture navigation bar.
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ params.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ keyboardShortcutsWindow.setAttributes(params);
+ keyboardShortcutsWindow.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
+ int bottom = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
+ View container = v.findViewById(R.id.keyboard_shortcuts_container);
+ container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
+ container.getPaddingRight(), bottom);
+ return WindowInsets.CONSUMED;
+ });
+ keyboardShortcutsWindow.setWindowAnimations(
+ R.style.KeyboardShortcutHelper_BottomSheetDialogAnimation);
}
private void populateKeyboardShortcutSearchList(LinearLayout keyboardShortcutsLayout) {
@@ -1256,10 +1269,10 @@ public final class KeyboardShortcutListSearch {
if (mContext.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT) {
lp.width = (int) (display.getWidth() * 0.8);
- lp.height = (int) (display.getHeight() * 0.7);
+ lp.height = (int) (display.getHeight() * 0.8);
} else {
lp.width = (int) (display.getWidth() * 0.7);
- lp.height = (int) (display.getHeight() * 0.8);
+ lp.height = (int) (display.getHeight() * 0.95);
}
window.setGravity(Gravity.BOTTOM);
window.setAttributes(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 080b5340c045..aa6bec1f06f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -46,9 +46,9 @@ import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
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.plugins.clocks.ClockController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -199,7 +199,7 @@ public class StatusBarStateControllerImpl implements
if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
combineFlows(
- mDeviceUnlockedInteractorLazy.get().isDeviceUnlocked(),
+ mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
mSceneInteractorLazy.get().getCurrentScene(),
this::calculateStateFromSceneFramework),
this::onStatusBarStateChanged);
@@ -468,13 +468,7 @@ public class StatusBarStateControllerImpl implements
/** Returns the id of the currently rendering clock */
public String getClockId() {
if (MigrateClocksToBlueprint.isEnabled()) {
- ClockController clock = mKeyguardClockInteractorLazy.get()
- .getCurrentClock().getValue();
- if (clock == null) {
- Log.e(TAG, "No clock is available");
- return KeyguardClockSwitch.MISSING_CLOCK_ID;
- }
- return clock.getConfig().getId();
+ return mKeyguardClockInteractorLazy.get().getRenderedClockId();
}
if (mClockSwitchView == null) {
@@ -653,11 +647,11 @@ public class StatusBarStateControllerImpl implements
}
private int calculateStateFromSceneFramework(
- boolean isDeviceUnlocked,
+ DeviceUnlockStatus deviceUnlockStatus,
SceneKey currentScene) {
SceneContainerFlag.isUnexpectedlyInLegacyMode();
- if (isDeviceUnlocked) {
+ if (deviceUnlockStatus.isUnlocked()) {
return StatusBarState.SHADE;
} else {
return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 8104755b5e7b..d2fe20d9c50c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -23,6 +23,7 @@ import android.view.View;
import com.android.systemui.CoreStartable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.lang.annotation.Retention;
@@ -30,6 +31,7 @@ import java.lang.annotation.Retention;
/**
* Sends updates to {@link StateListener}s about changes to the status bar state and dozing state
*/
+@Dependencies(CentralSurfaces.class)
public interface SysuiStatusBarStateController extends StatusBarStateController, CoreStartable {
// TODO: b/115739177 (remove this explicit ordering if we can)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index c4d9cbfcd55c..7a7cb7d3bfdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -123,7 +123,7 @@ interface ConnectivityModule {
labelRes = R.string.airplane_mode,
),
instanceId = uiEventLogger.getNewInstanceId(),
- policy = QSTilePolicy.Restricted(UserManager.DISALLOW_AIRPLANE_MODE),
+ policy = QSTilePolicy.Restricted(listOf(UserManager.DISALLOW_AIRPLANE_MODE)),
)
/** Inject AirplaneModeTile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index eb0870a5de44..2b7df7dc937d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -75,6 +75,8 @@ class NotificationTransitionAnimatorController(
private val notificationEntry = notification.entry
private val notificationKey = notificationEntry.sbn.key
+ override val isLaunching: Boolean = true
+
override var transitionContainer: ViewGroup
get() = notification.rootView as ViewGroup
set(ignored) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 8531eaa46804..1a223c110ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.os.SystemProperties
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -340,12 +341,41 @@ constructor(
var hasFilteredAnyNotifs = false
+ /**
+ * the [notificationMinimalismPrototype] will now show seen notifications on the locked
+ * shade by default, but this property read allows that to be quickly disabled for
+ * testing
+ */
+ private val minimalismShowOnLockedShade
+ get() =
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.show_on_locked_shade",
+ true
+ )
+
+ /**
+ * Encapsulates a definition of "being on the keyguard". Note that these two definitions
+ * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
+ * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
+ * is any state where the keyguard has not been dismissed, including locked shade and
+ * occluded lock screen.
+ *
+ * Returning false for locked shade and occluded states means that this filter will
+ * allow seen notifications to appear in the locked shade.
+ */
+ private fun isOnKeyguard(): Boolean =
+ if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) {
+ statusBarStateController.state == StatusBarState.KEYGUARD
+ } else {
+ keyguardRepository.isKeyguardShowing()
+ }
+
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
when {
// Don't apply filter if the setting is disabled
!unseenFilterEnabled -> false
// Don't apply filter if the keyguard isn't currently showing
- !keyguardRepository.isKeyguardShowing() -> false
+ !isOnKeyguard() -> false
// Don't apply the filter if the notification is unseen
unseenNotifications.contains(entry) -> false
// Don't apply the filter to (non-promoted) group summaries
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index adcbbfbde002..968b591b9a09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.util.DrawableDumpKt;
@@ -102,6 +103,11 @@ public class FooterView extends StackScrollerDecorView {
setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null);
}
+ /** Set the visibility of the "Manage"/"History" button to {@code visible}. */
+ public void setManageOrHistoryButtonVisible(boolean visible) {
+ mManageOrHistoryButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
/**
* Set the visibility of the "Clear all" button to {@code visible}. Animate the change if
* {@code animate} is true.
@@ -274,14 +280,23 @@ public class FooterView extends StackScrollerDecorView {
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
- if (isVisible) {
- mManageOrHistoryButton.setVisibility(View.GONE);
- mClearAllButton.setVisibility(View.GONE);
- mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ // In the refactored code, hiding the buttons is handled in the FooterViewModel
+ if (FooterViewRefactor.isEnabled()) {
+ if (isVisible) {
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
} else {
- mManageOrHistoryButton.setVisibility(View.VISIBLE);
- mClearAllButton.setVisibility(View.VISIBLE);
- mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ if (isVisible) {
+ mManageOrHistoryButton.setVisibility(View.GONE);
+ mClearAllButton.setVisibility(View.GONE);
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
+ mClearAllButton.setVisibility(View.VISIBLE);
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
}
}
@@ -443,6 +458,12 @@ public class FooterView extends StackScrollerDecorView {
*/
public boolean hideContent;
+ /**
+ * When true, skip animating Y on the next #animateTo.
+ * Once true, remains true until reset in #animateTo.
+ */
+ public boolean resetY = false;
+
@Override
public void copyFrom(ViewState viewState) {
super.copyFrom(viewState);
@@ -459,5 +480,17 @@ public class FooterView extends StackScrollerDecorView {
footerView.setContentVisibleAnimated(!hideContent);
}
}
+
+ @Override
+ public void animateTo(View child, AnimationProperties properties) {
+ if (child instanceof FooterView) {
+ // Must set animateY=false before super.animateTo, which checks for animateY
+ if (resetY) {
+ properties.getAnimationFilter().animateY = false;
+ resetY = false;
+ }
+ }
+ super.animateTo(child, properties);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 65ab4fdeb77b..637cadde6fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -141,8 +141,12 @@ object FooterViewBinder {
}
}
- // NOTE: The manage/history button is always visible as long as the footer is visible, no
- // need to update the visibility here.
+ launch {
+ viewModel.manageOrHistoryButton.isVisible.collect { isVisible ->
+ // NOTE: This visibility change is never animated.
+ footer.setManageOrHistoryButtonVisible(isVisible.value)
+ }
+ }
}
private suspend fun bindMessage(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index b23ef356c1c0..90fb7285e939 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -34,6 +34,7 @@ import java.util.Optional
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -45,12 +46,33 @@ class FooterViewModel(
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
+ /** A message to show instead of the footer buttons. */
+ val message: FooterMessageViewModel =
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications,
+ )
+
+ private val clearAllButtonVisible =
+ activeNotificationsInteractor.hasClearableNotifications
+ .combine(message.isVisible) { hasClearableNotifications, isMessageVisible ->
+ if (isMessageVisible) {
+ // If the message is visible, the button never is
+ false
+ } else {
+ hasClearableNotifications
+ }
+ }
+ .distinctUntilChanged()
+
+ /** The button for clearing notifications. */
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
labelId = flowOf(R.string.clear_all_notifications_text),
accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
- activeNotificationsInteractor.hasClearableNotifications
+ clearAllButtonVisible
.sample(
// TODO(b/322167853): This check is currently duplicated in
// NotificationListViewModel, but instead it should be a field in
@@ -61,9 +83,9 @@ class FooterViewModel(
::Pair
)
.onStart { emit(Pair(false, false)) }
- ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) ->
+ ) { clearAllButtonVisible, (isShadeFullyExpanded, animationsEnabled) ->
val shouldAnimate = isShadeFullyExpanded && animationsEnabled
- AnimatableEvent(hasClearableNotifications, shouldAnimate)
+ AnimatableEvent(clearAllButtonVisible, shouldAnimate)
}
.toAnimatedValueFlow(),
)
@@ -77,18 +99,16 @@ class FooterViewModel(
else R.string.manage_notifications_text
}
+ /** The button for managing notification settings or opening notification history. */
val manageOrHistoryButton: FooterButtonViewModel =
FooterButtonViewModel(
labelId = manageOrHistoryButtonText,
accessibilityDescriptionId = manageOrHistoryButtonText,
- isVisible = flowOf(AnimatedValue.NotAnimating(true)),
- )
-
- val message: FooterMessageViewModel =
- FooterMessageViewModel(
- messageId = R.string.unlock_to_see_notif_text,
- iconId = R.drawable.ic_friction_lock_closed,
- isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications,
+ isVisible =
+ // Hide the manage button if the message is visible
+ message.isVisible.map { messageVisible ->
+ AnimatedValue.NotAnimating(!messageVisible)
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index eb6c7b520037..9e0b16cfb312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1767,7 +1767,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
this(context, attrs, context);
- Log.wtf(TAG, "This constructor shouldn't be called");
+ Log.e(TAG, "This constructor shouldn't be called");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
new file mode 100644
index 000000000000..0344b32dd6ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the heads-up cycling flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationHeadsUpCycling {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationContentAlphaOptimization()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 5b9eb21bf5e9..0bb871b23d27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -742,7 +742,7 @@ public class AmbientState implements Dumpable {
pw.println("mHideSensitive=" + mHideSensitive);
pw.println("mShadeExpanded=" + mShadeExpanded);
pw.println("mClearAllInProgress=" + mClearAllInProgress);
- pw.println("mStatusBarState=" + mStatusBarState);
+ pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println("mExpansionChanging=" + mExpansionChanging);
pw.println("mPanelFullWidth=" + mIsSmallScreen);
pw.println("mPulsing=" + mPulsing);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 03a108287087..0c248f5949cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -30,7 +30,7 @@ public class AnimationFilter {
public static final int NO_DELAY = -1;
boolean animateAlpha;
boolean animateX;
- boolean animateY;
+ public boolean animateY;
ArraySet<View> animateYViews = new ArraySet<>();
boolean animateZ;
boolean animateHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 5dc37e0525da..92c597cf384e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -1429,7 +1430,7 @@ public class NotificationChildrenContainer extends ViewGroup
if (singleLineView != null) {
minExpandHeight += singleLineView.getHeight();
} else {
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (AsyncHybridViewInflation.isEnabled()) {
minExpandHeight += mMinSingleLineHeight;
} else {
Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3367dc427f43..82559dec9f86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -113,6 +113,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -135,6 +138,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
@@ -143,7 +147,9 @@ import java.util.function.Consumer;
/**
* A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
*/
-public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {
+public class NotificationStackScrollLayout
+ extends ViewGroup
+ implements Dumpable, NotificationScrollView {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -218,6 +224,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
private final StackScrollAlgorithm mStackScrollAlgorithm;
private final AmbientState mAmbientState;
+ private final ScrollViewFields mScrollViewFields = new ScrollViewFields();
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
@@ -230,7 +237,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateAnimator mStateAnimator;
- private boolean mAnimationsEnabled;
+ // TODO(b/332732878): call setAnimationsEnabled with scene container enabled, then remove this
+ private boolean mAnimationsEnabled = SceneContainerFlag.isEnabled();
private boolean mChangePositionInProgress;
private boolean mChildTransferInProgress;
@@ -589,7 +597,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Override
public boolean isScrolledToTop() {
if (SceneContainerFlag.isEnabled()) {
- return mController.isPlaceholderScrolledToTop();
+ return mScrollViewFields.isScrolledToTop();
} else {
return mOwnScrollY == 0;
}
@@ -854,7 +862,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
drawDebugInfo(canvas, y, Color.MAGENTA,
/* label= */ "mContentHeight = " + y);
- drawDebugInfo(canvas, mRoundedRectClippingBottom, Color.DKGRAY,
+ y = mRoundedRectClippingBottom;
+ drawDebugInfo(canvas, y, Color.DKGRAY,
/* label= */ "mRoundedRectClippingBottom) = " + y);
}
@@ -1130,6 +1139,48 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
+ @Override
+ public View asView() {
+ return this;
+ }
+
+ @Override
+ public void setScrolledToTop(boolean scrolledToTop) {
+ mScrollViewFields.setScrolledToTop(scrolledToTop);
+ }
+
+ @Override
+ public void setStackTop(float stackTop) {
+ mScrollViewFields.setStackTop(stackTop);
+ // TODO(b/332574413): replace the following with using stackTop
+ updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+ }
+
+ @Override
+ public void setStackBottom(float stackBottom) {
+ mScrollViewFields.setStackBottom(stackBottom);
+ }
+
+ @Override
+ public void setHeadsUpTop(float headsUpTop) {
+ mScrollViewFields.setHeadsUpTop(headsUpTop);
+ }
+
+ @Override
+ public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setSyntheticScrollConsumer(consumer);
+ }
+
+ @Override
+ public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setStackHeightConsumer(consumer);
+ }
+
+ @Override
+ public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) {
+ mScrollViewFields.setHeadsUpHeightConsumer(consumer);
+ }
+
/**
* @param listener to be notified after the location of Notification children might have
* changed.
@@ -1380,6 +1431,31 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mOnStackYChanged = onStackYChanged;
}
+ @Override
+ public void setExpandFraction(float expandFraction) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ final float oldFraction = mAmbientState.getExpansionFraction();
+ final boolean wasExpanding = oldFraction != 0f && oldFraction != 1f;
+ final boolean nowExpanding = expandFraction != 0f && expandFraction != 1f;
+
+ // need to enter 'expanding' state before handling the new expand fraction, and then
+ if (nowExpanding && !wasExpanding) {
+ onExpansionStarted();
+ mController.checkSnoozeLeavebehind();
+ }
+
+ // Update the expand progress between started/stopped events
+ mAmbientState.setExpansionFraction(expandFraction);
+ // TODO(b/332577544): don't convert to height which then converts to the fraction again
+ setExpandedHeight(expandFraction * getHeight());
+
+ // expansion stopped event requires that the expandFraction has already been updated
+ if (!nowExpanding && wasExpanding) {
+ setCheckForLeaveBehind(false);
+ onExpansionStopped();
+ }
+ }
+
/**
* Update the height of the panel.
*
@@ -1792,6 +1868,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void updateImeInset(WindowInsets windowInsets) {
mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (mFooterView != null && mFooterView.getViewState() != null) {
+ // Do not animate footer Y when showing IME so that after IME hides, the footer
+ // appears at the correct Y. Once resetY is true, it remains true (even when IME
+ // hides, where mImeInset=0) until reset in FooterViewState#animateTo.
+ ((FooterView.FooterViewState) mFooterView.getViewState()).resetY |= mImeInset > 0;
+ }
+
if (mForcedScroll != null) {
updateForcedScroll();
}
@@ -2314,7 +2397,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
- mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
+ mScrollViewFields.sendStackHeight(height);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
@@ -2904,6 +2987,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public void setAnimationsEnabled(boolean animationsEnabled) {
+ // TODO(b/332732878): remove the initial value of this field once the setter is called
mAnimationsEnabled = animationsEnabled;
updateNotificationAnimationStates();
if (!animationsEnabled) {
@@ -3553,7 +3637,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
protected boolean isInsideQsHeader(MotionEvent ev) {
if (SceneContainerFlag.isEnabled()) {
- return ev.getY() < mController.getPlaceholderTop();
+ return ev.getY() < mScrollViewFields.getScrimClippingShape().getBounds().getTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4086,7 +4170,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
// to it so that it can scroll the stack and scrim accordingly.
if (SceneContainerFlag.isEnabled()) {
float diff = endPosition - layoutEnd;
- mController.sendSyntheticScrollToSceneFramework(diff);
+ mScrollViewFields.sendSyntheticScroll(diff);
}
setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
mDisallowScrollingInThisMotion = true;
@@ -4969,6 +5053,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
elapsedRealtime - mLastUpdateSidePaddingElapsedRealtime);
println(pw, "isSmallLandscapeLockscreenEnabled", mIsSmallLandscapeLockscreenEnabled);
mNotificationStackSizeCalculator.dump(pw, args);
+ mScrollViewFields.dump(pw);
});
pw.println();
pw.println("Contents:");
@@ -5509,8 +5594,40 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Set rounded rect clipping bounds on this view.
*/
+ @Override
+ public void setScrimClippingShape(@Nullable ShadeScrimShape shape) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return;
+ mScrollViewFields.setScrimClippingShape(shape);
+ mShouldUseRoundedRectClipping = shape != null;
+ mRoundedClipPath.reset();
+ if (shape != null) {
+ ShadeScrimBounds bounds = shape.getBounds();
+ mRoundedRectClippingLeft = (int) bounds.getLeft();
+ mRoundedRectClippingTop = (int) bounds.getTop();
+ mRoundedRectClippingRight = (int) bounds.getRight();
+ mRoundedRectClippingBottom = (int) bounds.getBottom();
+ mBgCornerRadii[0] = shape.getTopRadius();
+ mBgCornerRadii[1] = shape.getTopRadius();
+ mBgCornerRadii[2] = shape.getTopRadius();
+ mBgCornerRadii[3] = shape.getTopRadius();
+ mBgCornerRadii[4] = shape.getBottomRadius();
+ mBgCornerRadii[5] = shape.getBottomRadius();
+ mBgCornerRadii[6] = shape.getBottomRadius();
+ mBgCornerRadii[7] = shape.getBottomRadius();
+ mRoundedClipPath.addRoundRect(
+ bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
+ mBgCornerRadii, Path.Direction.CW);
+ }
+ invalidate();
+ }
+
+ /**
+ * Set rounded rect clipping bounds on this view.
+ */
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
int bottomRadius) {
+ SceneContainerFlag.assertInLegacyMode();
if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
&& mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
&& mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5588,6 +5705,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* Should we use rounded rect clipping
*/
private void updateUseRoundedRectClipping() {
+ if (SceneContainerFlag.isEnabled()) return;
// We don't want to clip notifications when QS is expanded, because incoming heads up on
// the bottom would be clipped otherwise
boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade;
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 59901aca0425..06479e5a8e0e 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
@@ -83,6 +83,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
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.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
@@ -125,7 +126,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -189,7 +189,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final Provider<WindowRootView> mWindowRootView;
- private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -742,7 +741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
NotificationListViewBinder viewBinder,
ShadeController shadeController,
Provider<WindowRootView> windowRootView,
- NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -795,7 +793,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
- mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -1124,6 +1121,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public boolean isAddOrRemoveAnimationPending() {
+ SceneContainerFlag.assertInLegacyMode();
return mView != null && mView.isAddOrRemoveAnimationPending();
}
@@ -1163,29 +1161,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
}
- /** Send internal notification expansion to the scene container framework. */
- public void sendSyntheticScrollToSceneFramework(Float delta) {
- mStackAppearanceInteractor.setSyntheticScroll(delta);
- }
-
- /** Get the y-coordinate of the top bound of the stack. */
- public float getPlaceholderTop() {
- return mStackAppearanceInteractor.getShadeScrimBounds().getValue().getTop();
- }
-
- /**
- * Returns whether the notification stack is scrolled to the top; i.e., it cannot be scrolled
- * down any further.
- */
- public boolean isPlaceholderScrolledToTop() {
- return mStackAppearanceInteractor.getScrolledToTop().getValue();
- }
-
- /** Set the intrinsic height of the stack content without additional padding. */
- public void setIntrinsicContentHeight(float intrinsicContentHeight) {
- mStackAppearanceInteractor.setStackHeight(intrinsicContentHeight);
- }
-
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
@@ -1264,6 +1239,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setScrollingEnabled(boolean enabled) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setScrollingEnabled(enabled);
}
@@ -1284,6 +1260,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void updateTopPadding(float qsHeight, boolean animate) {
+ SceneContainerFlag.assertInLegacyMode();
mView.updateTopPadding(qsHeight, animate);
}
@@ -1386,11 +1363,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void onExpansionStarted() {
+ SceneContainerFlag.assertInLegacyMode();
mView.onExpansionStarted();
checkSnoozeLeavebehind();
}
public void onExpansionStopped() {
+ SceneContainerFlag.assertInLegacyMode();
mView.setCheckForLeaveBehind(false);
mView.onExpansionStopped();
}
@@ -1519,6 +1498,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setExpandedHeight(float expandedHeight) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setExpandedHeight(expandedHeight);
}
@@ -1794,6 +1774,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
int bottomRadius) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 1b53cbed8354..5bd4c758d678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -17,14 +17,15 @@
package com.android.systemui.statusbar.notification.stack
import android.content.res.Resources
+import android.os.SystemProperties
import android.util.Log
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
import com.android.systemui.Flags.notificationMinimalismPrototype
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -177,8 +178,8 @@ constructor(
// TODO: Avoid making this split shade assumption by simply checking the stack for media
val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
- val isMediaShowingInStack = isMediaShowing && !splitShadeStateController
- .shouldUseSplitNotificationShade(resources)
+ val isMediaShowingInStack =
+ isMediaShowing && !splitShadeStateController.shouldUseSplitNotificationShade(resources)
log { "\tGet maxNotifWithoutSavingSpace ---" }
val maxNotifWithoutSavingSpace =
@@ -378,8 +379,17 @@ constructor(
}
fun updateResources() {
- maxKeyguardNotifications = if (notificationMinimalismPrototype()) 1
- else infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
+ maxKeyguardNotifications =
+ infiniteIfNegative(
+ if (notificationMinimalismPrototype()) {
+ SystemProperties.getInt(
+ "persist.notification_minimalism_prototype.lock_screen_max_notifs",
+ 1
+ )
+ } else {
+ resources.getInteger(R.integer.keyguard_max_notification_count)
+ }
+ )
maxNotificationsExcludesMedia = notificationMinimalismPrototype()
dividerHeight =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
new file mode 100644
index 000000000000..edac5ede1e91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import android.util.IndentingPrintWriter
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
+import com.android.systemui.util.printSection
+import com.android.systemui.util.println
+import java.util.function.Consumer
+
+/**
+ * This is a state holder object used by [NSSL][NotificationStackScrollLayout] to contain states
+ * provided by the `NotificationScrollViewBinder` to the `NotificationScrollView`.
+ *
+ * Unlike AmbientState, no class other than NSSL should ever have access to this class in any way.
+ * These fields are effectively NSSL's private fields.
+ */
+class ScrollViewFields {
+ /** Used to produce the clipping path */
+ var scrimClippingShape: ShadeScrimShape? = null
+ /** Y coordinate in view pixels of the top of the notification stack */
+ var stackTop: Float = 0f
+ /**
+ * Y coordinate in view pixels above which the bottom of the notification stack / shelf / footer
+ * must be.
+ */
+ var stackBottom: Float = 0f
+ /** Y coordinate in view pixels of the top of the HUN */
+ var headsUpTop: Float = 0f
+ /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */
+ var isScrolledToTop: Boolean = true
+
+ /**
+ * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding
+ * notification in view), that scroll amount can be sent here and it will be handled by the
+ * placeholder
+ */
+ var syntheticScrollConsumer: Consumer<Float>? = null
+ /**
+ * Any time the stack height is recalculated, it should be updated here to be used by the
+ * placeholder
+ */
+ var stackHeightConsumer: Consumer<Float>? = null
+ /**
+ * Any time the heads up height is recalculated, it should be updated here to be used by the
+ * placeholder
+ */
+ var headsUpHeightConsumer: Consumer<Float>? = null
+
+ /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */
+ fun sendSyntheticScroll(syntheticScroll: Float) =
+ syntheticScrollConsumer?.accept(syntheticScroll)
+ /** send the [stackHeight] to the [stackHeightConsumer], if present. */
+ fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight)
+ /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */
+ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight)
+
+ fun dump(pw: IndentingPrintWriter) {
+ pw.printSection("StackViewStates") {
+ pw.println("scrimClippingShape", scrimClippingShape)
+ pw.println("stackTop", stackTop)
+ pw.println("stackBottom", stackBottom)
+ pw.println("headsUpTop", headsUpTop)
+ pw.println("isScrolledToTop", isScrolledToTop)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index 462547ea2385..b04737936166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -27,8 +27,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
*/
@SysUISingleton
class NotificationPlaceholderRepository @Inject constructor() {
- /** The bounds of the notification shade scrim / container in the current scene. */
- val shadeScrimBounds = MutableStateFlow(ShadeScrimBounds())
+ /**
+ * The bounds of the notification shade scrim / container in the current scene.
+ *
+ * When `null`, clipping should not be applied to notifications.
+ */
+ val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
/**
* The y-coordinate in px of top of the contents of the notification stack. This value can be
@@ -44,7 +48,7 @@ class NotificationPlaceholderRepository @Inject constructor() {
val headsUpTop = MutableStateFlow(0f)
/** height made available to the notifications in the size-constrained mode of lock screen. */
- val constrainedAvailableSpace = MutableStateFlow(0f)
+ val constrainedAvailableSpace = MutableStateFlow(0)
/**
* Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
index 938e43e4a2d4..8a9da69079d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt
@@ -35,7 +35,7 @@ class NotificationViewHeightRepository @Inject constructor() {
val stackHeight = MutableStateFlow(0f)
/** The height in px of the current heads up notification. */
- val activeHeadsUpRowHeight = MutableStateFlow(0f)
+ val headsUpHeight = MutableStateFlow(0f)
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 32562f109252..a5b4f5f2bb4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -42,7 +42,7 @@ constructor(
shadeInteractor: ShadeInteractor,
) {
/** The bounds of the notification stack in the current scene. */
- val shadeScrimBounds: StateFlow<ShadeScrimBounds> =
+ val shadeScrimBounds: StateFlow<ShadeScrimBounds?> =
placeholderRepository.shadeScrimBounds.asStateFlow()
/**
@@ -59,8 +59,8 @@ constructor(
isExpandingFromHeadsUp,
) { shadeMode, isExpandingFromHeadsUp ->
ShadeScrimRounding(
- roundTop = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
- roundBottom = shadeMode != ShadeMode.Single,
+ isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
+ isBottomRounded = shadeMode != ShadeMode.Single,
)
}
.distinctUntilChanged()
@@ -72,15 +72,28 @@ constructor(
*/
val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow()
+ /** The height in px of the contents of the HUN. */
+ val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow()
+
/** The y-coordinate in px of top of the contents of the notification stack. */
val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow()
+ /** The y-coordinate in px of bottom of the contents of the notification stack. */
+ val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow()
+
+ /** The height of the keyguard's available space bounds */
+ val constrainedAvailableSpace: StateFlow<Int> =
+ placeholderRepository.constrainedAvailableSpace.asStateFlow()
+
/**
* Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
* further.
*/
val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow()
+ /** The y-coordinate in px of bottom of the contents of the HUN. */
+ val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow()
+
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
@@ -89,8 +102,8 @@ constructor(
val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow()
/** Sets the position of the notification stack in the current scene. */
- fun setShadeScrimBounds(bounds: ShadeScrimBounds) {
- check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
+ fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
+ check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
placeholderRepository.shadeScrimBounds.value = bounds
}
@@ -99,9 +112,19 @@ constructor(
viewHeightRepository.stackHeight.value = height
}
+ /** Sets the height of heads up notification. */
+ fun setHeadsUpHeight(height: Float) {
+ viewHeightRepository.headsUpHeight.value = height
+ }
+
/** Sets the y-coord in px of the top of the contents of the notification stack. */
- fun setStackTop(startY: Float) {
- placeholderRepository.stackTop.value = startY
+ fun setStackTop(stackTop: Float) {
+ placeholderRepository.stackTop.value = stackTop
+ }
+
+ /** Sets the y-coord in px of the bottom of the contents of the notification stack. */
+ fun setStackBottom(stackBottom: Float) {
+ placeholderRepository.stackBottom.value = stackBottom
}
/** Sets whether the notification stack is scrolled to the top. */
@@ -113,4 +136,12 @@ constructor(
fun setSyntheticScroll(delta: Float) {
viewHeightRepository.syntheticScroll.value = delta
}
+
+ fun setConstrainedAvailableSpace(height: Int) {
+ placeholderRepository.constrainedAvailableSpace.value = height
+ }
+
+ fun setHeadsUpTop(headsUpTop: Float) {
+ placeholderRepository.headsUpTop.value = headsUpTop
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 448127a55b89..832e69080f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -29,4 +29,16 @@ data class ShadeScrimBounds(
) {
/** The current height of the notification container. */
val height: Float = bottom - top
+
+ fun minus(leftOffset: Int = 0, topOffset: Int = 0) =
+ if (leftOffset == 0 && topOffset == 0) {
+ this
+ } else {
+ ShadeScrimBounds(
+ left = this.left - leftOffset,
+ top = this.top - topOffset,
+ right = this.right - leftOffset,
+ bottom = this.bottom - topOffset,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
index 621dd0c49f54..e86fd1c7cdc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimClipping.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.shared.model
-/** Models the clipping rounded rectangle of the notification stack */
+/** Models the clipping rounded rectangle of the notification stack, where the rounding is binary */
data class ShadeScrimClipping(
val bounds: ShadeScrimBounds = ShadeScrimBounds(),
val rounding: ShadeScrimRounding = ShadeScrimRounding()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
index 2fe265f35604..9d4231c8967b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimRounding.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack.shared.model
/** Models the corner rounds of the notification stack. */
data class ShadeScrimRounding(
/** Whether the top corners of the notification stack should be rounded. */
- val roundTop: Boolean = false,
+ val isTopRounded: Boolean = false,
/** Whether the bottom corners of the notification stack should be rounded. */
- val roundBottom: Boolean = false,
+ val isBottomRounded: Boolean = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt
new file mode 100644
index 000000000000..e6f0647a059d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimShape.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.shared.model
+
+/** Models the clipping rounded rectangle of the notification stack, with corner radii in px */
+data class ShadeScrimShape(
+ val bounds: ShadeScrimBounds = ShadeScrimBounds(),
+ /** radius in px of the top corners */
+ val topRadius: Int,
+ /** radius in px of the bottom corners */
+ val bottomRadius: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
new file mode 100644
index 000000000000..ac00d3b27493
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.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.notification.stack.ui.view
+
+import android.view.View
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
+import java.util.function.Consumer
+
+/**
+ * This view (interface) is the view which scrolls and positions the heads up notification and
+ * notification stack, but is otherwise agnostic to the content.
+ */
+interface NotificationScrollView {
+ /**
+ * Since this is an interface rather than a literal View, this provides cast-like access to the
+ * underlying view.
+ */
+ fun asView(): View
+
+ /** Set the clipping bounds used when drawing */
+ fun setScrimClippingShape(shape: ShadeScrimShape?)
+
+ /** set the y position in px of the top of the stack in this view's coordinates */
+ fun setStackTop(stackTop: Float)
+
+ /** set the y position in px of the bottom of the stack in this view's coordinates */
+ fun setStackBottom(stackBottom: Float)
+
+ /** set the y position in px of the top of the HUN in this view's coordinates */
+ fun setHeadsUpTop(headsUpTop: Float)
+
+ /** set whether the view has been scrolled all the way to the top */
+ fun setScrolledToTop(scrolledToTop: Boolean)
+
+ /** Set a consumer for synthetic scroll events */
+ fun setSyntheticScrollConsumer(consumer: Consumer<Float>?)
+ /** Set a consumer for stack height changed events */
+ fun setStackHeightConsumer(consumer: Consumer<Float>?)
+ /** Set a consumer for heads up height changed events */
+ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?)
+
+ /** sets that scrolling is allowed */
+ fun setScrollingEnabled(enabled: Boolean)
+
+ /** sets the current expand fraction */
+ fun setExpandFraction(expandFraction: Float)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
new file mode 100644
index 000000000000..047b560afbc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import android.util.Log
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onLayoutChanged
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.launchAndDispose
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/** Binds the [NotificationScrollView]. */
+@SysUISingleton
+class NotificationScrollViewBinder
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
+ private val view: NotificationScrollView,
+ private val viewModel: NotificationScrollViewModel,
+ private val configuration: ConfigurationState,
+) : FlowDumperImpl(dumpManager) {
+
+ private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
+
+ private fun updateViewPosition() {
+ val trueView = view.asView()
+ if (trueView.top != 0) {
+ Log.w("NSSL", "Expected $trueView to have top==0")
+ }
+ viewLeftOffset.value = trueView.left
+ }
+
+ fun bindWhileAttached(): DisposableHandle {
+ return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) { bind() }
+ }
+ }
+
+ suspend fun bind() = coroutineScope {
+ launchAndDispose {
+ updateViewPosition()
+ view.asView().onLayoutChanged { updateViewPosition() }
+ }
+
+ launch {
+ viewModel
+ .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+ .collect { view.setScrimClippingShape(it) }
+ }
+
+ launch { viewModel.stackTop.collect { view.setStackTop(it) } }
+ launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
+ launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
+ launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
+ launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
+ launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
+
+ launchAndDispose {
+ view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
+ view.setStackHeightConsumer(viewModel.stackHeightConsumer)
+ view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer)
+ DisposableHandle {
+ view.setSyntheticScrollConsumer(null)
+ view.setStackHeightConsumer(null)
+ view.setHeadsUpHeightConsumer(null)
+ }
+ }
+ }
+
+ /** flow of the scrim clipping radius */
+ private val scrimRadius: Flow<Int>
+ get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt
deleted file mode 100644
index d6d31db86e61..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackViewBinder.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.stack.AmbientState
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-/** Binds the NSSL/Controller/AmbientState to their ViewModel. */
-@SysUISingleton
-class NotificationStackViewBinder
-@Inject
-constructor(
- @Main private val mainImmediateDispatcher: CoroutineDispatcher,
- private val ambientState: AmbientState,
- private val view: NotificationStackScrollLayout,
- private val controller: NotificationStackScrollLayoutController,
- private val viewModel: NotificationStackAppearanceViewModel,
- private val configuration: ConfigurationState,
-) {
-
- fun bindWhileAttached(): DisposableHandle {
- return view.repeatWhenAttached(mainImmediateDispatcher) {
- repeatOnLifecycle(Lifecycle.State.CREATED) { bind() }
- }
- }
-
- suspend fun bind() = coroutineScope {
- launch {
- combine(viewModel.shadeScrimClipping, clipRadius, ::Pair).collect {
- (clipping, clipRadius) ->
- val (bounds, rounding) = clipping
- val viewLeft = controller.view.left
- val viewTop = controller.view.top
- controller.setRoundedClippingBounds(
- bounds.left.roundToInt() - viewLeft,
- bounds.top.roundToInt() - viewTop,
- bounds.right.roundToInt() - viewLeft,
- bounds.bottom.roundToInt() - viewTop,
- if (rounding.roundTop) clipRadius else 0,
- if (rounding.roundBottom) clipRadius else 0,
- )
- }
- }
-
- launch {
- viewModel.stackTop.collect {
- controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
- }
- }
-
- launch {
- var wasExpanding = false
- viewModel.expandFraction.collect { expandFraction ->
- val nowExpanding = expandFraction != 0f && expandFraction != 1f
- if (nowExpanding && !wasExpanding) {
- controller.onExpansionStarted()
- }
- ambientState.expansionFraction = expandFraction
- controller.expandedHeight = expandFraction * controller.view.height
- if (!nowExpanding && wasExpanding) {
- controller.onExpansionStopped()
- }
- wasExpanding = nowExpanding
- }
- }
-
- launch { viewModel.isScrollable.collect { controller.setScrollingEnabled(it) } }
- }
-
- private val clipRadius: Flow<Int>
- get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 4a096a89848a..741102bcd574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -21,11 +21,14 @@ import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.communalHub
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -49,7 +52,7 @@ constructor(
private val sceneContainerFlags: SceneContainerFlags,
private val controller: NotificationStackScrollLayoutController,
private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
- private val notificationStackViewBinder: NotificationStackViewBinder,
+ private val notificationScrollViewBinder: NotificationScrollViewBinder,
@Main private val mainImmediateDispatcher: CoroutineDispatcher,
) {
@@ -137,10 +140,12 @@ constructor(
}
}
- launch {
- burnInParams
- .flatMapLatest { params -> viewModel.translationY(params) }
- .collect { y -> controller.setTranslationY(y) }
+ if (!SceneContainerFlag.isEnabled) {
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y -> controller.setTranslationY(y) }
+ }
}
launch { viewModel.translationX.collect { x -> controller.translationX = x } }
@@ -162,28 +167,22 @@ constructor(
}
if (sceneContainerFlags.isEnabled()) {
- disposables += notificationStackViewBinder.bindWhileAttached()
+ disposables += notificationScrollViewBinder.bindWhileAttached()
}
controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- insets
- }
- disposables += DisposableHandle { view.setOnApplyWindowInsetsListener(null) }
- // Required to capture keyguard media changes and ensure the notification count is correct
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- viewModel.notificationStackChanged()
- }
- view.addOnLayoutChangeListener(layoutChangeListener)
- disposables += DisposableHandle { view.removeOnLayoutChangeListener(layoutChangeListener) }
+ disposables += view.onLayoutChanged { viewModel.notificationStackChanged() }
return disposables
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 071127c03202..516ec319ceb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -26,17 +26,17 @@ import com.android.systemui.scene.shared.model.Scenes.Shade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@SysUISingleton
-class NotificationStackAppearanceViewModel
+class NotificationScrollViewModel
@Inject
constructor(
dumpManager: DumpManager,
@@ -80,16 +80,48 @@ constructor(
.dumpWhileCollecting("expandFraction")
/** The bounds of the notification stack in the current scene. */
- val shadeScrimClipping: Flow<ShadeScrimClipping> =
+ private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
combine(
stackAppearanceInteractor.shadeScrimBounds,
stackAppearanceInteractor.shadeScrimRounding,
- ::ShadeScrimClipping
- )
+ ) { bounds, rounding ->
+ bounds?.let { ShadeScrimClipping(it, rounding) }
+ }
.dumpWhileCollecting("stackClipping")
+ fun shadeScrimShape(
+ cornerRadius: Flow<Int>,
+ viewLeftOffset: Flow<Int>
+ ): Flow<ShadeScrimShape?> =
+ combine(shadeScrimClipping, cornerRadius, viewLeftOffset) { clipping, radius, leftOffset ->
+ if (clipping == null) return@combine null
+ ShadeScrimShape(
+ bounds = clipping.bounds.minus(leftOffset = leftOffset),
+ topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
+ bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
+ )
+ }
+ .dumpWhileCollecting("shadeScrimShape")
+
/** The y-coordinate in px of top of the contents of the notification stack. */
- val stackTop: StateFlow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
+ val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop")
+ /** The y-coordinate in px of bottom of the contents of the notification stack. */
+ val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom")
+ /**
+ * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any
+ * further.
+ */
+ val scrolledToTop: Flow<Boolean> =
+ stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop")
+ /** The y-coordinate in px of bottom of the contents of the HUN. */
+ val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop")
+
+ /** Receives the amount (px) that the stack should scroll due to internal expansion. */
+ val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
+ /** Receives the height of the contents of the notification stack. */
+ val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight
+ /** Receives the height of the heads up notification. */
+ val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight
/** Whether the notification stack is scrollable or not. */
val isScrollable: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 477f139bcd7d..b284179d42b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -26,8 +27,10 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -37,67 +40,72 @@ import kotlinx.coroutines.flow.Flow
class NotificationsPlaceholderViewModel
@Inject
constructor(
+ dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
private val keyguardInteractor: KeyguardInteractor,
-) {
+) : FlowDumperImpl(dumpManager) {
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
/** DEBUG: whether the debug logging should be output. */
val isDebugLoggingEnabled: Boolean = flags.isEnabled()
- /**
- * Notifies that the bounds of the notification placeholder have changed.
- *
- * @param top The position of the top of the container in its window coordinate system, in
- * pixels.
- * @param bottom The position of the bottom of the container in its window coordinate system, in
- * pixels.
- */
- fun onBoundsChanged(
- left: Float,
+ /** Notifies that the bounds of the notification scrim have changed. */
+ fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
+ interactor.setShadeScrimBounds(bounds)
+ }
+
+ /** Notifies that the bounds of the notification placeholder have changed. */
+ fun onStackBoundsChanged(
top: Float,
- right: Float,
bottom: Float,
) {
keyguardInteractor.setNotificationContainerBounds(
NotificationContainerBounds(top = top, bottom = bottom)
)
- interactor.setShadeScrimBounds(
- ShadeScrimBounds(top = top, bottom = bottom, left = left, right = right)
- )
+ interactor.setStackTop(top)
+ interactor.setStackBottom(bottom)
+ }
+
+ /** Sets the available space */
+ fun onConstrainedAvailableSpaceChanged(height: Int) {
+ interactor.setConstrainedAvailableSpace(height)
+ }
+
+ fun onHeadsUpTopChanged(headsUpTop: Float) {
+ interactor.setHeadsUpTop(headsUpTop)
}
/** Corner rounding of the stack */
- val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding
+ val shadeScrimRounding: Flow<ShadeScrimRounding> =
+ interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
* point the notification stack should become scrollable.
*/
- val stackHeight = interactor.stackHeight
+ val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight")
+
+ /** The height in px of the contents of the HUN. */
+ val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight")
/**
* The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
* is open.
*/
- val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion.dumpValue("expandFraction")
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
* necessary to scroll up to keep expanding the notification.
*/
- val syntheticScroll: Flow<Float> = interactor.syntheticScroll
-
- /** Sets the y-coord in px of the top of the contents of the notification stack. */
- fun onContentTopChanged(padding: Float) {
- interactor.setStackTop(padding)
- }
+ val syntheticScroll: Flow<Float> =
+ interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll")
/** Sets whether the notification stack is scrolled to the top. */
fun setScrolledToTop(scrolledToTop: Boolean) {
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 9f576066cc98..0486ef5bca51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -19,6 +19,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -60,7 +61,9 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransition
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
@@ -97,6 +100,7 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
+ private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -163,23 +167,35 @@ constructor(
)
.dumpWhileCollecting("isShadeLocked")
+ @VisibleForTesting
+ val paddingTopDimen: Flow<Int> =
+ interactor.configurationBasedDimensions
+ .map {
+ when {
+ !it.useSplitShade -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("paddingTopDimen")
+
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
.map {
val marginTop =
- if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop
+ when {
+ // y position of the NSSL in the window needs to be 0 under scene container
+ SceneContainerFlag.isEnabled -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
ConfigurationBasedDimensions(
marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
marginEnd = it.marginHorizontal,
marginBottom = it.marginBottom,
marginTop = marginTop,
useSplitShade = it.useSplitShade,
- paddingTop =
- if (it.useSplitShade) {
- marginTop
- } else {
- 0
- },
)
}
.distinctUntilChanged()
@@ -338,16 +354,16 @@ constructor(
combine(
isOnLockscreenWithoutShade,
keyguardInteractor.notificationContainerBounds,
- configurationBasedDimensions,
+ paddingTopDimen,
interactor.topPosition
.sampleCombine(
keyguardTransitionInteractor.isInTransitionToAnyState,
shadeInteractor.qsExpansion,
)
.onStart { emit(Triple(0f, false, 0f)) }
- ) { onLockscreen, bounds, config, (top, isInTransitionToAnyState, qsExpansion) ->
+ ) { onLockscreen, bounds, paddingTop, (top, isInTransitionToAnyState, qsExpansion) ->
if (onLockscreen) {
- bounds.copy(top = bounds.top - config.paddingTop)
+ bounds.copy(top = bounds.top - paddingTop)
} else {
// When QS expansion > 0, it should directly set the top padding so do not
// animate it
@@ -539,6 +555,8 @@ constructor(
* translated as the keyguard fades out.
*/
fun translationY(params: BurnInParameters): Flow<Float> {
+ // with SceneContainer, x translation is handled by views, y is handled by compose
+ SceneContainerFlag.assertInLegacyMode()
return combine(
aodBurnInViewModel
.movement(params)
@@ -571,8 +589,11 @@ constructor(
.dumpWhileCollecting("translationX")
private val availableHeight: Flow<Float> =
- bounds
- .map { it.bottom - it.top }
+ if (SceneContainerFlag.isEnabled) {
+ notificationStackAppearanceInteractor.constrainedAvailableSpace.map { it.toFloat() }
+ } else {
+ bounds.map { it.bottom - it.top }
+ }
.distinctUntilChanged()
.dumpWhileCollecting("availableHeight")
@@ -634,6 +655,5 @@ constructor(
val marginEnd: Int,
val marginBottom: Int,
val useSplitShade: Boolean,
- val paddingTop: Int,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a1fae03a2090..2651d2eed404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -103,8 +103,8 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
/**
* Returns an ActivityOptions bundle created using the given parameters.
*
- * @param displayId The ID of the display to launch the activity in. Typically this would
- * be the display the status bar is on.
+ * @param displayId The ID of the display to launch the activity in. Typically this would
+ * be the display the status bar is on.
* @param animationAdapter The animation adapter used to start this activity, or {@code null}
* for the default animation.
*/
@@ -197,7 +197,7 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
void onKeyguardViewManagerStatesUpdated();
- /** */
+ /** */
boolean getCommandQueuePanelsEnabled();
void showWirelessChargingAnimation(int batteryLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2798dbfc62e4..1d6b744a7bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,7 +28,6 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.newAodTransition;
-import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -169,6 +168,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -595,6 +595,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final SceneContainerFlags mSceneContainerFlags;
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
+
/**
* Public constructor for CentralSurfaces.
*
@@ -706,7 +708,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags
+ SceneContainerFlags sceneContainerFlags,
+ BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -802,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
mSceneContainerFlags = sceneContainerFlags;
+ mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -837,7 +841,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
mLightRevealScrim = lightRevealScrim;
- if (predictiveBackSysui()) {
+ if (PredictiveBackSysUiFlag.isEnabled()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
}
@@ -1084,6 +1088,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
+ if (mSceneContainerFlags.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mBrightnessMirrorShowingInteractor.isShowing(),
+ this::setBrightnessMirrorShowing
+ );
+ }
}
/**
@@ -1285,10 +1295,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
- (visible) -> {
- mBrightnessMirrorVisible = visible;
- updateScrimController();
- });
+ this::setBrightnessMirrorShowing);
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragmentLegacy) {
@@ -1347,6 +1354,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
}
+ private void setBrightnessMirrorShowing(boolean showing) {
+ mBrightnessMirrorVisible = showing;
+ updateScrimController();
+ }
/**
* When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f.
@@ -3061,7 +3072,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
public void onConfigChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
- if (predictiveBackSysui()) {
+ if (PredictiveBackSysUiFlag.isEnabled()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 495b4e1e14cd..4c3c7d56df50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -421,9 +422,12 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
public void updateHeader(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
float headerVisibleAmount = 1.0f;
- if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
- || row.showingPulsing()) {
- headerVisibleAmount = mAppearFraction;
+ // To fix the invisible HUN group header issue
+ if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
+ || row.showingPulsing()) {
+ headerVisibleAmount = mAppearFraction;
+ }
}
row.setHeaderVisibleAmount(headerVisibleAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt
new file mode 100644
index 000000000000..74d6ba57a8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the predictive back flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PredictiveBackSysUiFlag {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_PREDICTIVE_BACK_SYSUI
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.predictiveBackSysui()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index afd2415ad7a8..5f26702ad867 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -662,7 +662,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* show if any subsequent events are to be handled.
*/
if (beginShowingBouncer(event)) {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer, "StatusBarKeyguardViewManager.onPanelExpansionChanged");
+ } else {
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
+ }
}
if (!primaryBouncerIsOrWillBeShowing()) {
@@ -716,7 +721,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// The keyguard might be showing (already). So we need to hide it.
if (!primaryBouncerIsShowing()) {
mCentralSurfaces.hideKeyguard();
- mPrimaryBouncerInteractor.show(true);
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer, "StatusBarKeyguardViewManager.showBouncerOrKeyguard");
+ } else {
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ }
} else {
Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
}
@@ -778,7 +788,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void showPrimaryBouncer(boolean scrimmed) {
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
- mPrimaryBouncerInteractor.show(scrimmed);
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer, "StatusBarKeyguardViewManager.showPrimaryBouncer");
+ } else {
+ mPrimaryBouncerInteractor.show(scrimmed);
+ }
}
updateStates();
}
@@ -873,13 +888,23 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
- mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer, "StatusBarKeyguardViewManager.dismissWithAction");
+ } else {
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ }
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
mPrimaryBouncerInteractor.setDismissAction(
mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
- mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer, "StatusBarKeyguardViewManager.dismissWithAction");
+ } else {
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ }
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 82d9fc7d0152..2a921dc38416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -42,6 +42,7 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
import com.android.systemui.Dependency;
import com.android.systemui.animation.DialogTransitionAnimator;
@@ -71,7 +72,7 @@ import javax.inject.Inject;
* and dismisses itself when it receives the broadcast.
*/
public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigChangedCallback {
- protected static final int DEFAULT_THEME = R.style.Theme_SystemUI_Dialog;
+ public static final int DEFAULT_THEME = R.style.Theme_SystemUI_Dialog;
// TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
private static final String FLAG_TABLET_DIALOG_WIDTH =
"persist.systemui.flag_tablet_dialog_width";
@@ -141,7 +142,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* When you just need a dialog, call this.
*/
public SystemUIDialog create() {
- return create(new DialogDelegate<>(){}, mContext);
+ return create(new DialogDelegate<>(){}, mContext, DEFAULT_THEME);
}
/** Creates a new instance of {@link SystemUIDialog} with no customized behavior.
@@ -149,7 +150,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* When you just need a dialog created with a specific {@link Context}, call this.
*/
public SystemUIDialog create(Context context) {
- return create(new DialogDelegate<>(){}, context);
+ return create(new DialogDelegate<>(){}, context, DEFAULT_THEME);
}
/**
@@ -159,7 +160,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* When you need to customize the dialog, pass it a delegate.
*/
public SystemUIDialog create(Delegate delegate, Context context) {
- return create((DialogDelegate<SystemUIDialog>) delegate, context);
+ return create(delegate, context, DEFAULT_THEME);
+ }
+ public SystemUIDialog create(Delegate delegate, Context context, @StyleRes int theme) {
+ return create((DialogDelegate<SystemUIDialog>) delegate, context, theme);
}
public SystemUIDialog create(Delegate delegate) {
@@ -167,10 +171,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate,
- Context context) {
+ Context context, @StyleRes int theme) {
return new SystemUIDialog(
context,
- DEFAULT_THEME,
+ theme,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
mSystemUIDialogManager,
mSysUiState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 317c0634a364..8f00b43e79f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -44,9 +44,6 @@ interface MobileConnectionRepository {
/** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */
val carrierId: StateFlow<Int>
- /** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
- val inflateSignalStrength: StateFlow<Boolean>
-
/**
* The table log buffer created for this connection. Will have the name "MobileConnectionLog
* [subId]"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 90cdfeb05d4e..af34a57c7242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -43,7 +43,6 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.F
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
@@ -68,17 +67,6 @@ class DemoMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _carrierId.value)
- private val _inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val inflateSignalStrength =
- _inflateSignalStrength
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = "",
- columnName = "inflate",
- _inflateSignalStrength.value
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
-
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly =
_isEmergencyOnly
@@ -203,16 +191,7 @@ class DemoMobileConnectionRepository(
.logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
.stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
- override val numberOfLevels =
- _inflateSignalStrength
- .map { shouldInflate ->
- if (shouldInflate) {
- DEFAULT_NUM_LEVELS + 1
- } else {
- DEFAULT_NUM_LEVELS
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+ override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
override val dataEnabled = MutableStateFlow(true)
@@ -247,7 +226,8 @@ class DemoMobileConnectionRepository(
_carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID
- _inflateSignalStrength.value = event.inflateStrength
+ numberOfLevels.value =
+ if (event.inflateStrength) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
cdmaRoaming.value = event.roaming
_isRoaming.value = event.roaming
@@ -278,6 +258,7 @@ class DemoMobileConnectionRepository(
carrierName.value = NetworkNameModel.SubscriptionDerived(CARRIER_MERGED_NAME)
// TODO(b/276943904): is carrierId a thing with carrier merged networks?
_carrierId.value = INVALID_SUBSCRIPTION_ID
+ numberOfLevels.value = event.numberOfLevels
cdmaRoaming.value = false
_primaryLevel.value = event.level
_cdmaLevel.value = event.level
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index cb99d11e79b7..2bc3bcbc8bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -165,7 +165,6 @@ class CarrierMergedConnectionRepository(
override val isRoaming = MutableStateFlow(false).asStateFlow()
override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow()
- override val inflateSignalStrength = MutableStateFlow(false).asStateFlow()
override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
override val isInService = MutableStateFlow(true).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index bb0af775f820..b085d8046b12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -291,21 +291,6 @@ class FullMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
- override val inflateSignalStrength =
- activeRepo
- .flatMapLatest { it.inflateSignalStrength }
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = "",
- columnName = "inflate",
- initialValue = activeRepo.value.inflateSignalStrength.value,
- )
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- activeRepo.value.inflateSignalStrength.value
- )
-
override val numberOfLevels =
activeRepo
.flatMapLatest { it.numberOfLevels }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 2cbe965b0ea9..5ab2ae899370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -302,10 +302,8 @@ class MobileConnectionRepositoryImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
- override val inflateSignalStrength = systemUiCarrierConfig.shouldInflateSignalStrength
-
override val numberOfLevels =
- inflateSignalStrength
+ systemUiCarrierConfig.shouldInflateSignalStrength
.map { shouldInflate ->
if (shouldInflate) {
DEFAULT_NUM_LEVELS + 1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index cbebfd0587ab..9d194cfca350 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -367,11 +367,8 @@ class MobileIconInteractorImpl(
combine(
level,
isInService,
- connectionRepository.inflateSignalStrength,
- ) { level, isInService, inflate ->
- if (isInService) {
- if (inflate) level + 1 else level
- } else 0
+ ) { level, isInService ->
+ if (isInService) level else 0
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index d6b8fd4fec87..5d3b9eff593f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -94,7 +94,8 @@ sealed interface SignalIconModel : Diffable<SignalIconModel> {
}
override fun logFully(row: TableRowLogger) {
- row.logChange("numLevels", "HELLO")
+ // Satellite icon has only 3 levels, unchanging
+ row.logChange(COL_NUM_LEVELS, "3")
row.logChange(COL_TYPE, "s")
row.logChange(COL_LEVEL, level)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 55a0f59bf461..9cdecef3f6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -17,9 +17,12 @@ package com.android.systemui.statusbar.policy
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import java.io.PrintWriter
import javax.inject.Inject
/*
@@ -27,7 +30,9 @@ import javax.inject.Inject
* succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
*/
@SysUISingleton
-class AvalancheController @Inject constructor() {
+class AvalancheController @Inject constructor(
+ dumpManager: DumpManager,
+) : Dumpable {
private val tag = "AvalancheController"
private val debug = false
@@ -54,22 +59,26 @@ class AvalancheController @Inject constructor() {
// For debugging only
@VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
- /**
- * Run or delay Runnable for given HeadsUpEntry
- */
- fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ init {
+ dumpManager.registerNormalDumpable(tag, /* module */ this)
+ }
+
+ /** Run or delay Runnable for given HeadsUpEntry */
+ fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
}
val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
-
+ if (entry == null) {
+ log { "Entry is NULL, stop update." }
+ return;
+ }
if (debug) {
debugRunnableLabelMap[runnable] = label
}
-
if (isShowing(entry)) {
- log {"$fn => [update showing]" }
+ log { "$fn => [update showing]" }
runnable.run()
} else if (entry in nextMap) {
log { "$fn => [update next]" }
@@ -164,9 +173,7 @@ class AvalancheController @Inject constructor() {
}
}
- /**
- * Return true if entry is waiting to show.
- */
+ /** Return true if entry is waiting to show. */
fun isWaiting(key: String): Boolean {
if (!NotificationThrottleHun.isEnabled) {
return false
@@ -179,9 +186,7 @@ class AvalancheController @Inject constructor() {
return false
}
- /**
- * Return list of keys for huns waiting
- */
+ /** Return list of keys for huns waiting */
fun getWaitingKeys(): MutableList<String> {
if (!NotificationThrottleHun.isEnabled) {
return mutableListOf()
@@ -254,12 +259,15 @@ class AvalancheController @Inject constructor() {
}
}
- // TODO(b/315362456) expose as dumpable for bugreports
+ private fun getStateStr(): String {
+ return "SHOWING: ${getKey(headsUpEntryShowing)}" +
+ "\tNEXT LIST: $nextListStr\tMAP: $nextMapStr" +
+ "\tDROP: $dropSetStr"
+ }
+
private fun logState(reason: String) {
- log { "state $reason" }
- log { "showing: " + getKey(headsUpEntryShowing) }
- log { "next list: $nextListStr map: $nextMapStr" }
- log { "drop: $dropSetStr" }
+ log { "REASON $reason" }
+ log { getStateStr() }
}
private val dropSetStr: String
@@ -298,4 +306,8 @@ class AvalancheController @Inject constructor() {
}
return entry.mEntry!!.key
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("AvalancheController: ${getStateStr()}")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 20a82a403eb7..d99af2ddb95d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -165,6 +165,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void showNotification(@NonNull NotificationEntry entry) {
HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry);
+ mLogger.logShowNotificationRequest(entry);
+
Runnable runnable = () -> {
// TODO(b/315362456) log outside runnable too
mLogger.logShowNotification(entry);
@@ -219,6 +221,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null);
+
Runnable runnable = () -> {
updateNotificationInternal(key, shouldHeadsUpAgain);
};
@@ -378,8 +382,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logRemoveEntryRequest(key);
Runnable runnable = () -> {
+ mLogger.logRemoveEntry(key);
+
if (headsUpEntry == null) {
return;
}
@@ -566,8 +573,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-
+ mLogger.logUnpinEntryRequest(key);
Runnable runnable = () -> {
+ mLogger.logUnpinEntry(key);
+
setEntryPinned(headsUpEntry, false /* isPinned */);
// maybe it got un sticky
headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
@@ -886,6 +895,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* Clear any pending removal runnables.
*/
public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
Runnable runnable = () -> {
final boolean removed = cancelAutoRemovalCallbackInternal();
@@ -900,6 +910,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
@NonNull String reason) {
+ mLogger.logAutoRemoveRequest(this.mEntry, reason);
Runnable runnable = () -> {
long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 13f76feca9fd..7d920eab73fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -27,6 +27,7 @@ import android.widget.FrameLayout;
import com.android.systemui.res.R;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.settings.brightness.ToggleSlider;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
@@ -38,8 +39,7 @@ import java.util.function.Consumer;
/**
* Controls showing and hiding of the brightness mirror.
*/
-public class BrightnessMirrorController
- implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
+public class BrightnessMirrorController implements MirrorController {
private final NotificationShadeWindowView mStatusBarWindow;
private final Consumer<Boolean> mVisibilityCallback;
@@ -71,6 +71,7 @@ public class BrightnessMirrorController
updateResources();
}
+ @Override
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
@@ -78,16 +79,14 @@ public class BrightnessMirrorController
mDepthController.setBrightnessMirrorVisible(true);
}
+ @Override
public void hideMirror() {
mVisibilityCallback.accept(false);
mNotificationPanel.setAlpha(255, true /* animate */);
mDepthController.setBrightnessMirrorVisible(false);
}
- /**
- * Set the location and size of the mirror container to match that of the slider in QS
- * @param original the original view in QS
- */
+ @Override
public void setLocationAndSize(View original) {
original.getLocationInWindow(mInt2Cache);
@@ -112,6 +111,7 @@ public class BrightnessMirrorController
}
}
+ @Override
public ToggleSlider getToggleSlider() {
return mToggleSliderController;
}
@@ -176,8 +176,4 @@ public class BrightnessMirrorController
public void onUiModeChanged() {
reinflate();
}
-
- public interface BrightnessMirrorListener {
- void onBrightnessMirrorReinflated(View brightnessMirror);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index f6154afec273..a30660645990 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -58,6 +58,14 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logShowNotificationRequest(entry: NotificationEntry) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ }, {
+ "request: show notification $str1"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -76,6 +84,15 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "request: reschedule auto remove of $str1 reason: $str2"
+ })
+ }
+
fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -86,6 +103,15 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ str2 = reason ?: "unknown"
+ }, {
+ "request: cancel auto remove of $str1 reason: $str2"
+ })
+ }
+
fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -95,6 +121,38 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logRemoveEntryRequest(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "request: remove entry $str1"
+ })
+ }
+
+ fun logRemoveEntry(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "remove entry $str1"
+ })
+ }
+
+ fun logUnpinEntryRequest(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "request: unpin entry $str1"
+ })
+ }
+
+ fun logUnpinEntry(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "unpin entry $str1"
+ })
+ }
+
fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
@@ -112,6 +170,16 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ bool1 = alert
+ bool2 = hasEntry
+ }, {
+ "request: update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2"
+ })
+ }
+
fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 7a570275d868..db4e605dc9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -14,6 +14,12 @@
package com.android.systemui.statusbar.policy
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.os.UserManager.DISALLOW_CAMERA_TOGGLE
+import android.os.UserManager.DISALLOW_CONFIG_LOCATION
+import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE
+import android.os.UserManager.DISALLOW_SHARE_LOCATION
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -38,6 +44,11 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyToggleTileMapper
import com.android.systemui.qs.tiles.impl.uimodenight.domain.UiModeNightTileMapper
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor
@@ -47,6 +58,7 @@ import com.android.systemui.qs.tiles.impl.work.domain.interactor.WorkModeTileUse
import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
import com.android.systemui.qs.tiles.impl.work.ui.WorkModeTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.res.R
@@ -74,6 +86,8 @@ interface PolicyModule {
const val ALARM_TILE_SPEC = "alarm"
const val UIMODENIGHT_TILE_SPEC = "dark"
const val WORK_MODE_TILE_SPEC = "work"
+ const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
+ const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
/** Inject flashlight config */
@Provides
@@ -120,6 +134,13 @@ interface PolicyModule {
labelRes = R.string.quick_settings_location_label,
),
instanceId = uiEventLogger.getNewInstanceId(),
+ policy =
+ QSTilePolicy.Restricted(
+ listOf(
+ DISALLOW_SHARE_LOCATION,
+ DISALLOW_CONFIG_LOCATION
+ )
+ )
)
/** Inject LocationTile into tileViewModelMap in QSModule */
@@ -234,6 +255,72 @@ interface PolicyModule {
stateInteractor,
mapper,
)
+
+ /** Inject camera toggle config */
+ @Provides
+ @IntoMap
+ @StringKey(CAMERA_TOGGLE_TILE_SPEC)
+ fun provideCameraToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(CAMERA_TOGGLE_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_camera_access_icon_off,
+ labelRes = R.string.quick_settings_camera_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ policy = QSTilePolicy.Restricted(listOf(DISALLOW_CAMERA_TOGGLE)),
+ )
+
+ /** Inject camera toggle tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(CAMERA_TOGGLE_TILE_SPEC)
+ fun provideCameraToggleTileViewModel(
+ factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>,
+ mapper: SensorPrivacyToggleTileMapper.Factory,
+ stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory,
+ userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory,
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(CAMERA_TOGGLE_TILE_SPEC),
+ userActionInteractor.create(CAMERA),
+ stateInteractor.create(CAMERA),
+ mapper.create(SensorPrivacyTileResources.CameraPrivacyTileResources),
+ )
+
+ /** Inject microphone toggle config */
+ @Provides
+ @IntoMap
+ @StringKey(MIC_TOGGLE_TILE_SPEC)
+ fun provideMicrophoneToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(MIC_TOGGLE_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_mic_access_off,
+ labelRes = R.string.quick_settings_mic_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ policy = QSTilePolicy.Restricted(listOf(DISALLOW_MICROPHONE_TOGGLE)),
+ )
+
+ /** Inject microphone toggle tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(MIC_TOGGLE_TILE_SPEC)
+ fun provideMicrophoneToggleTileViewModel(
+ factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>,
+ mapper: SensorPrivacyToggleTileMapper.Factory,
+ stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory,
+ userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory,
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(MIC_TOGGLE_TILE_SPEC),
+ userActionInteractor.create(MICROPHONE),
+ stateInteractor.create(MICROPHONE),
+ mapper.create(SensorPrivacyTileResources.MicrophonePrivacyTileResources),
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 860068c137a3..0d53277e51dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -20,6 +20,7 @@ import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.screenshareNotificationHidingBugFix;
import android.annotation.MainThread;
import android.app.IActivityManager;
@@ -31,6 +32,7 @@ import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.RemoteException;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
@@ -316,6 +318,10 @@ public class SensitiveNotificationProtectionControllerImpl
return false;
}
+ if (screenshareNotificationHidingBugFix() && UserHandle.isCore(sbn.getUid())) {
+ return false; // do not hide/redact notifications from system uid
+ }
+
// Only protect/redact notifications if the developer has not explicitly set notification
// visibility as public and users has not adjusted default channel visibility to private
boolean notificationRequestsRedaction = entry.isNotificationVisibilityPrivate();
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 2f2c4b0530fb..b6f54331b29f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -31,6 +31,7 @@ import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.AccessibilityNodeInfo
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DimenRes
@@ -57,6 +58,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
+import java.time.Duration
import javax.inject.Inject
/**
@@ -228,6 +230,18 @@ constructor(
chipInnerView.contentDescription =
"$loadedIconDesc${newInfo.text.loadText(context)}$endItemDesc"
chipInnerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+ // Set minimum duration between content changes to 1 second in order to announce quick
+ // state changes.
+ chipInnerView.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.minDurationBetweenContentChanges = Duration.ofMillis(1000)
+ }
+ }
maybeGetAccessibilityFocus(newInfo, currentView)
// ---- Haptics ----
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index e977014e00f2..aea739dcb6cc 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -20,11 +20,10 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
-import android.content.Context
-import android.os.Handler
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
+import com.android.app.tracing.TraceUtils.traceAsync
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,12 +35,13 @@ import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Comp
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.race
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -59,13 +59,13 @@ import kotlinx.coroutines.withTimeout
class FoldLightRevealOverlayAnimation
@Inject
constructor(
- private val context: Context,
- @UnfoldBg private val bgHandler: Handler,
+ @UnfoldBg private val bgDispatcher: CoroutineDispatcher,
private val deviceStateRepository: DeviceStateRepository,
private val powerInteractor: PowerInteractor,
@Background private val applicationScope: CoroutineScope,
private val animationStatusRepository: AnimationStatusRepository,
- private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+ private val controllerFactory: FullscreenLightRevealAnimationController.Factory,
+ private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider
) : FullscreenLightRevealAnimation {
private val revealProgressValueAnimator: ValueAnimator =
@@ -79,7 +79,7 @@ constructor(
override fun init() {
// This method will be called only on devices where this animation is enabled,
// so normally this thread won't be created
- if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+ if (!foldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) {
return
}
@@ -91,7 +91,6 @@ constructor(
)
controller.init()
- val bgDispatcher = bgHandler.asCoroutineDispatcher("@UnfoldBg Handler")
applicationScope.launch(bgDispatcher) {
powerInteractor.screenPowerState.collect {
if (it == ScreenPowerState.SCREEN_ON) {
@@ -109,14 +108,21 @@ constructor(
if (!areAnimationEnabled.first() || !isFolded) {
return@flow
}
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
- }
- playFoldLightRevealOverlayAnimation()
+ race(
+ {
+ traceAsync(TAG, "prepareAndPlayFoldAnimation()") {
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
+ }
+ playFoldLightRevealOverlayAnimation()
+ }
+ },
+ { waitForGoToSleep() }
+ )
}
.catchTimeoutAndLog()
.onCompletion {
@@ -135,9 +141,13 @@ constructor(
readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
}
- private suspend fun waitForScreenTurnedOn() {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
- }
+ private suspend fun waitForScreenTurnedOn() =
+ traceAsync(TAG, "waitForScreenTurnedOn()") {
+ powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ }
+
+ private suspend fun waitForGoToSleep() =
+ traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() }
private suspend fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 9bd0e324a964..35228508f727 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.SystemProperties
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
import com.android.systemui.dagger.qualifiers.Application
@@ -175,6 +176,12 @@ class UnfoldTransitionModule {
fun provideDisplaySwitchLatencyLogger(): DisplaySwitchLatencyLogger =
DisplaySwitchLatencyLogger()
+ @Provides
+ @Singleton
+ fun provideFoldLockSettingAvailabilityProvider(
+ context: Context
+ ): FoldLockSettingAvailabilityProvider = FoldLockSettingAvailabilityProvider(context.resources)
+
@Module
interface Bindings {
@Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
index 909a18be4b9e..97d957dde01b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
@@ -17,8 +17,14 @@
package com.android.systemui.util.kotlin
import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
/**
* Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for
@@ -42,3 +48,22 @@ suspend fun DisposableHandle.awaitCancellationThenDispose() {
dispose()
}
}
+
+/**
+ * This will [launch], run [onLaunch] to get a [DisposableHandle], and finally
+ * [awaitCancellationThenDispose][DisposableHandle.awaitCancellationThenDispose]. This can be used
+ * to structure self-disposing code which attaches listeners, for example in ViewBinders:
+ * ```
+ * suspend fun bind(view: MyView, viewModel: MyViewModel) = coroutineScope {
+ * launchAndDispose {
+ * view.setOnClickListener { viewModel.handleClick() }
+ * DisposableHandle { view.setOnClickListener(null) }
+ * }
+ * }
+ * ```
+ */
+inline fun CoroutineScope.launchAndDispose(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ crossinline onLaunch: () -> DisposableHandle
+): Job = launch(context, start) { onLaunch().awaitCancellationThenDispose() }
diff --git a/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt b/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt
new file mode 100644
index 000000000000..38c6d7f8fc80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.utils
+
+import com.android.settingslib.RestrictedLockUtils
+
+/**
+ * Models a possible policy restriction.
+ *
+ * @see RestrictedLockUtils.checkIfRestrictionEnforced
+ */
+sealed interface PolicyRestriction {
+ data object NoRestriction : PolicyRestriction
+
+ data class Restricted(val admin: RestrictedLockUtils.EnforcedAdmin) : PolicyRestriction
+}
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 b0c8a4a2d478..dc73344fafe6 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
@@ -23,7 +23,7 @@ import com.android.settingslib.volume.data.repository.MediaControllerChange
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+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
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index ea4c082f4660..eebb6fb42488 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -21,7 +21,7 @@ import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.media.dialog.MediaOutputDialogManager
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -33,10 +33,10 @@ constructor(
private val mediaOutputDialogManager: MediaOutputDialogManager,
) {
- fun onBarClick(session: MediaDeviceSession, isPlaybackActive: Boolean, expandable: Expandable) {
- if (isPlaybackActive) {
+ fun onBarClick(sessionWithPlayback: SessionWithPlayback?, expandable: Expandable) {
+ if (sessionWithPlayback?.playback?.isActive == true) {
mediaOutputDialogManager.createAndShowWithController(
- session.packageName,
+ sessionWithPlayback.session.packageName,
false,
expandable.dialogController()
)
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 e60139ecf9cc..41ad0358eae2 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
@@ -25,8 +25,8 @@ import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
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 javax.inject.Inject
import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
index ddc078421b9a..22c160d4fd5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
@@ -16,6 +16,8 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+
/** Models a pair of local and remote [MediaDeviceSession]s. */
data class MediaDeviceSessions(
val local: MediaDeviceSession?,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt
index 2a2ce796a2b7..eca33155dc46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+package com.android.systemui.volume.panel.component.mediaoutput.shared.model
import android.media.session.MediaSession
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt
new file mode 100644
index 000000000000..c4476fc26113
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.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.volume.panel.component.mediaoutput.shared.model
+
+import android.media.session.PlaybackState
+
+data class SessionWithPlayback(
+ val session: MediaDeviceSession,
+ val playback: PlaybackState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 2530a3a46384..fc9602e6017f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
-import android.media.session.PlaybackState
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -25,9 +24,8 @@ import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +45,6 @@ class MediaOutputViewModel
constructor(
private val context: Context,
@VolumePanelScope private val coroutineScope: CoroutineScope,
- private val volumePanelViewModel: VolumePanelViewModel,
private val actionsInteractor: MediaOutputActionsInteractor,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
interactor: MediaOutputInteractor,
@@ -129,14 +126,6 @@ constructor(
)
fun onBarClick(expandable: Expandable) {
- sessionWithPlayback.value?.let {
- actionsInteractor.onBarClick(it.session, it.playback.isActive, expandable)
- }
- volumePanelViewModel.dismissPanel()
+ actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
}
-
- private data class SessionWithPlayback(
- val session: MediaDeviceSession,
- val playback: PlaybackState,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index ceb769ea2ef6..f022039e9cde 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -81,15 +81,19 @@ constructor(
model = isEnabled,
iconColor =
Color.Attribute(
- if (isChecked)
+ if (isChecked) {
com.android.internal.R.attr.materialColorOnPrimaryContainer
- else com.android.internal.R.attr.materialColorOnSurfaceVariant
+ } else {
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ }
),
labelColor =
Color.Attribute(
- if (isChecked)
+ if (isChecked) {
com.android.internal.R.attr.materialColorOnSurface
- else com.android.internal.R.attr.materialColorOutline
+ } else {
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ }
),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 73c8bbfce6d9..8d8fa17bf986 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -21,7 +21,7 @@ import android.media.session.MediaController.PlaybackInfo
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 4e9a45635f7b..09e56c1a65a3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -20,8 +20,8 @@ import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
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.component.mediaoutput.domain.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isTheSameSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9bfc4ce49cc7..1568e8c011a1 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,8 +20,12 @@ import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.SetWallpaperFlags;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
+import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
@@ -134,6 +138,12 @@ public class ImageWallpaper extends WallpaperService {
mLongExecutor,
mLock,
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
+
+ @Override
+ public void onColorsProcessed() {
+ CanvasEngine.this.notifyColorsChanged();
+ }
+
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -183,8 +193,11 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onDestroy() {
- getDisplayContext().getSystemService(DisplayManager.class)
- .unregisterDisplayListener(this);
+ Context context = getDisplayContext();
+ if (context != null) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ if (displayManager != null) displayManager.unregisterDisplayListener(this);
+ }
mWallpaperLocalColorExtractor.cleanUp();
}
@@ -429,6 +442,12 @@ public class ImageWallpaper extends WallpaperService {
}
@Override
+ public @Nullable WallpaperColors onComputeColors() {
+ if (!offloadColorExtraction()) return null;
+ return mWallpaperLocalColorExtractor.onComputeColors();
+ }
+
+ @Override
public boolean supportsLocalColorExtraction() {
return true;
}
@@ -465,6 +484,12 @@ public class ImageWallpaper extends WallpaperService {
}
@Override
+ public void onDimAmountChanged(float dimAmount) {
+ if (!offloadColorExtraction()) return;
+ mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount);
+ }
+
+ @Override
public void onDisplayAdded(int displayId) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index e2ec8dc056ca..d37dfb49c5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -17,6 +17,8 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -66,6 +68,12 @@ public class WallpaperLocalColorExtractor {
private final List<RectF> mPendingRegions = new ArrayList<>();
private final Set<RectF> mProcessedRegions = new ArraySet<>();
+ private float mWallpaperDimAmount = 0f;
+ private WallpaperColors mWallpaperColors;
+
+ // By default we assume that colors were loaded from disk and don't need to be recomputed
+ private boolean mRecomputeColors = false;
+
@LongRunning
private final Executor mLongExecutor;
@@ -75,6 +83,12 @@ public class WallpaperLocalColorExtractor {
* Interface to handle the callbacks after the different steps of the color extraction
*/
public interface WallpaperLocalColorExtractorCallback {
+
+ /**
+ * Callback after the wallpaper colors have been computed
+ */
+ void onColorsProcessed();
+
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -129,7 +143,7 @@ public class WallpaperLocalColorExtractor {
if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -166,7 +180,8 @@ public class WallpaperLocalColorExtractor {
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
- recomputeColors();
+ if (offloadColorExtraction() && mRecomputeColors) recomputeColorsInternal();
+ recomputeLocalColors();
}
}
@@ -184,16 +199,66 @@ public class WallpaperLocalColorExtractor {
if (mPages == pages) return;
mPages = pages;
if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
- recomputeColors();
+ recomputeLocalColors();
}
}
}
- // helper to recompute colors, to be called in synchronized methods
- private void recomputeColors() {
+ /**
+ * Should be called when the dim amount of the wallpaper changes, to recompute the colors
+ */
+ public void onDimAmountChanged(float dimAmount) {
+ mLongExecutor.execute(() -> onDimAmountChangedSynchronized(dimAmount));
+ }
+
+ private void onDimAmountChangedSynchronized(float dimAmount) {
+ synchronized (mLock) {
+ if (mWallpaperDimAmount == dimAmount) return;
+ mWallpaperDimAmount = dimAmount;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * To be called by {@link ImageWallpaper.CanvasEngine#onComputeColors}. This will either
+ * return the current wallpaper colors, or if the bitmap is not yet loaded, return null and call
+ * {@link WallpaperLocalColorExtractorCallback#onColorsProcessed()} when the colors are ready.
+ */
+ public WallpaperColors onComputeColors() {
+ mLongExecutor.execute(this::onComputeColorsSynchronized);
+ return mWallpaperColors;
+ }
+
+ private void onComputeColorsSynchronized() {
+ synchronized (mLock) {
+ if (mRecomputeColors) return;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * helper to recompute main colors, to be called in synchronized methods
+ */
+ private void recomputeColorsInternal() {
+ if (mMiniBitmap == null) return;
+ mWallpaperColors = getWallpaperColors(mMiniBitmap, mWallpaperDimAmount);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed();
+ }
+
+ @VisibleForTesting
+ WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap, float dimAmount) {
+ return WallpaperColors.fromBitmap(bitmap, dimAmount);
+ }
+
+ /**
+ * helper to recompute local colors, to be called in synchronized methods
+ */
+ private void recomputeLocalColors() {
mPendingRegions.addAll(mProcessedRegions);
mProcessedRegions.clear();
- processColorsInternal();
+ processLocalColorsInternal();
}
/**
@@ -216,7 +281,7 @@ public class WallpaperLocalColorExtractor {
if (!wasActive && isActive()) {
mWallpaperLocalColorExtractorCallback.onActivated();
}
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -353,7 +418,7 @@ public class WallpaperLocalColorExtractor {
* then notify the callback with the resulting colors for these regions
* This method should only be called synchronously
*/
- private void processColorsInternal() {
+ private void processLocalColorsInternal() {
/*
* if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
* called, and thus the wallpaper is not yet loaded. In that case, exit, the function
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 572a6c12f3e1..0dbbe63b63bd 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -224,6 +224,5 @@
<instrumentation android:name="android.testing.TestableInstrumentation"
android:targetPackage="com.android.systemui.tests"
- android:label="Tests for SystemUI">
- </instrumentation>
+ android:label="Tests for SystemUI" />
</manifest>
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index cd2a62de1acc..2de5faf2be8c 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -23,6 +23,15 @@
<option name="force-root" value="true" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="framework-base-presubmit" />
<option name="test-tag" value="SystemUITests" />
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
new file mode 100644
index 000000000000..60bff17c8541
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
new file mode 100644
index 000000000000..ea768c0a04bd
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
new file mode 100644
index 000000000000..608e633d060b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
new file mode 100644
index 000000000000..608e633d060b
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 3d0d8fb4abe4..ca55dd8ba5fa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -142,6 +142,7 @@ class ClockEventControllerTest : SysuiTestCase() {
context.resources,
context,
mainExecutor,
+ IMMEDIATE,
bgExecutor,
clockBuffers,
withDeps.featureFlags,
@@ -329,7 +330,8 @@ class ClockEventControllerTest : SysuiTestCase() {
TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
- value = 0.4f
+ value = 0.4f,
+ transitionState = TransitionState.RUNNING,
)
yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index b0f0363a3e97..15764571ce02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -43,10 +43,10 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemType;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
index 95d5597ef645..d16db65334d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
@@ -27,7 +27,7 @@ import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import org.junit.Before;
import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 75a49d73cb59..41974f46ea7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -246,6 +246,8 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
*/
private class TestTransitionAnimatorController(override var transitionContainer: ViewGroup) :
ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
+
override fun createAnimatorState() =
TransitionAnimator.State(
top = 100,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
new file mode 100644
index 000000000000..c380a51f6a59
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.animation
+
+import android.animation.AnimatorSet
+import android.graphics.drawable.GradientDrawable
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.RecordedMotion
+import platform.test.motion.Sampling.Companion.evenlySampled
+import platform.test.motion.view.DrawableFeatureCaptures
+import platform.test.motion.view.ViewMotionTestRule
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TransitionAnimatorTest : SysuiTestCase() {
+ companion object {
+ private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
+
+ private val emulationSpec =
+ DeviceEmulationSpec(
+ DisplaySpec(
+ "phone",
+ width = 320,
+ height = 690,
+ densityDpi = 160,
+ )
+ )
+ }
+
+ private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
+ private val transitionAnimator =
+ TransitionAnimator(
+ ActivityTransitionAnimator.TIMINGS,
+ ActivityTransitionAnimator.INTERPOLATORS
+ )
+
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
+ @get:Rule(order = 2)
+ val motionRule =
+ ViewMotionTestRule<EmptyTestActivity>(
+ pathManager,
+ { activityRule.scenario },
+ context = context,
+ bitmapDiffer = null,
+ )
+
+ @Test
+ fun backgroundAnimation_whenLaunching() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator = setUpTest(backgroundLayer, isLaunching = true)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimation_whenReturning() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator = setUpTest(backgroundLayer, isLaunching = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimationWithoutFade_whenLaunching() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator =
+ setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimationWithoutFade_whenReturning() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator =
+ setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ private fun setUpTest(
+ backgroundLayer: GradientDrawable,
+ isLaunching: Boolean,
+ fadeWindowBackgroundLayer: Boolean = true,
+ ): AnimatorSet {
+ lateinit var transitionContainer: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) }
+ activity.setContentView(transitionContainer)
+ }
+ waitForIdleSync()
+
+ val controller = TestController(transitionContainer, isLaunching)
+ val animator =
+ transitionAnimator.createAnimator(
+ controller,
+ createEndState(transitionContainer),
+ backgroundLayer,
+ fadeWindowBackgroundLayer
+ )
+ return AnimatorSet().apply {
+ duration = animator.duration
+ play(animator)
+ }
+ }
+
+ private fun createEndState(container: ViewGroup): TransitionAnimator.State {
+ val containerLocation = IntArray(2)
+ container.getLocationOnScreen(containerLocation)
+ return TransitionAnimator.State(
+ left = containerLocation[0],
+ top = containerLocation[1],
+ right = containerLocation[0] + emulationSpec.display.width,
+ bottom = containerLocation[1] + emulationSpec.display.height,
+ topCornerRadius = 0f,
+ bottomCornerRadius = 0f
+ )
+ }
+
+ private fun recordMotion(
+ backgroundLayer: GradientDrawable,
+ animator: AnimatorSet
+ ): RecordedMotion {
+ return motionRule.checkThat(animator).record(
+ backgroundLayer,
+ evenlySampled(20),
+ visualCapture = null
+ ) {
+ capture(DrawableFeatureCaptures.bounds, "bounds")
+ capture(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+ capture(DrawableFeatureCaptures.alpha, "alpha")
+ }
+ }
+}
+
+/**
+ * A simple implementation of [TransitionAnimator.Controller] which throws if it is called outside
+ * of the main thread.
+ */
+private class TestController(
+ override var transitionContainer: ViewGroup,
+ override val isLaunching: Boolean
+) : TransitionAnimator.Controller {
+ override fun createAnimatorState(): TransitionAnimator.State {
+ val containerLocation = IntArray(2)
+ transitionContainer.getLocationOnScreen(containerLocation)
+ return TransitionAnimator.State(
+ left = containerLocation[0] + 100,
+ top = containerLocation[1] + 300,
+ right = containerLocation[0] + 200,
+ bottom = containerLocation[1] + 400,
+ topCornerRadius = 10f,
+ bottomCornerRadius = 20f
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index 3dcb3f89c730..5b6aee697fec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
@@ -65,7 +65,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[0]);
// touch at 90 degrees
@@ -73,7 +74,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[1]);
// touch at 180 degrees
@@ -81,7 +83,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[2]);
// touch at 270 degrees
@@ -89,7 +92,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[3]);
}
@@ -103,7 +107,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[1]);
// touch at 90 degrees -> 180 degrees
@@ -111,7 +116,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[2]);
// touch at 180 degrees -> 270 degrees
@@ -119,7 +125,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[3]);
// touch at 270 degrees -> 0 degrees
@@ -127,7 +134,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[0]);
}
@@ -141,7 +149,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[3]);
// touch at 90 degrees -> 0 degrees
@@ -149,7 +158,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[0]);
// touch at 180 degrees -> 90 degrees
@@ -157,7 +167,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[1]);
// touch at 270 degrees -> 180 degrees
@@ -165,7 +176,8 @@ public class UdfpsUtilsTest extends SysuiTestCase {
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL),
+ true /* rotatedToPortrait */
)
).isEqualTo(mTouchHints[2]);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index d4a0c8f74b14..30c5e6ed4812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -77,6 +77,8 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -85,7 +87,6 @@ import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import java.util.Optional
@@ -238,6 +239,8 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
+ kosmos.fakeSceneContainerFlags,
+ kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
keyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index ae20c703b93d..238a76eb7400 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -33,6 +33,7 @@ import com.airbnb.lottie.model.KeyPath
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -75,6 +76,8 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -233,6 +236,8 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
+ kosmos.fakeSceneContainerFlags,
+ kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
keyguardUpdateMonitor
@@ -347,6 +352,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
@Test
fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() {
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
setupTestConfiguration(
DeviceConfig.X_ALIGNED,
rotation = DisplayRotation.ROTATION_0,
@@ -388,6 +394,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
@Test
fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() {
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
setupTestConfiguration(
DeviceConfig.Y_ALIGNED,
rotation = DisplayRotation.ROTATION_0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
index 036d3c862ae0..4949716ad129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
index 31192841ec77..85e2a8d4b48e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index 479e62d4d724..a8f82eda51c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 17b612714fe2..12dfe97649d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
-import android.content.Context
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -121,23 +120,18 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
sysuiDialogFactory
)
- whenever(
- sysuiDialogFactory.create(
- any(SystemUIDialog.Delegate::class.java)
- )
+ whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0)
)
- .thenAnswer {
- SystemUIDialog(
- mContext,
- 0,
- SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogTransitionAnimator,
- it.getArgument(0)
- )
- }
+ }
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
@@ -163,6 +157,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
assertThat(recyclerView.adapter).isNotNull()
assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+ dialog.dismiss()
}
@Test
@@ -184,6 +179,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+ dialog.dismiss()
}
}
@@ -259,6 +255,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
assertThat(adapter.itemCount).isEqualTo(1)
assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+ dialog.dismiss()
}
}
@@ -283,6 +280,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isEqualTo(cachedHeight)
+ dialog.dismiss()
}
}
@@ -306,6 +304,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isGreaterThan(MATCH_PARENT)
+ dialog.dismiss()
}
}
@@ -331,6 +330,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
)
.isEqualTo(GONE)
+ dialog.dismiss()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
index da8f60a9b926..4aa6209fab3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index c8a2aa64ffa2..6d99c5b62e9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a8cd8c801a95..28cbcb435223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.pm.PackageInfo
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index ddf0b9a78165..eb735cbfec47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 45d20dcd9d11..8e5ddc79976f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -202,6 +203,36 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
}
@Test
+ public void testPassThroughEnterKeyEvent() {
+ KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent enterUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(enterDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(enterUp);
+ verify(mFalsingDataProvider, times(1)).onKeyEvent(enterUp);
+ }
+
+ @Test
+ public void testAvoidAKeyEvent() {
+ // Arbitrarily chose the "A" key, as it is not currently allowlisted. If this key is
+ // allowlisted in the future, please choose another key that will not be collected.
+ KeyEvent aKeyDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent aKeyUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(aKeyDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(aKeyUp);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 0353f87ae9b4..057b0a158cab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -282,6 +283,22 @@ public class FalsingDataProviderTest extends ClassifierTest {
}
@Test
+ public void test_isFromKeyboard_disallowedKey_false() {
+ KeyEvent eventDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+
+ //events have not come in yet
+ assertThat(mDataProvider.isFromKeyboard()).isFalse();
+
+ mDataProvider.onKeyEvent(eventDown);
+ mDataProvider.onKeyEvent(eventUp);
+ assertThat(mDataProvider.isFromKeyboard()).isTrue();
+ mDataProvider.onSessionEnd();
+ }
+
+ @Test
public void test_IsFromTrackpad() {
MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
index 901196f8bbba..ad7afa3c593f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
@@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import android.testing.AndroidTestingRunner;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -34,28 +36,28 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
+public class TimeLimitedInputEventBufferTest extends SysuiTestCase {
private static final long MAX_AGE_MS = 100;
- private TimeLimitedMotionEventBuffer mBuffer;
+ private TimeLimitedInputEventBuffer<InputEvent> mBuffer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
+ mBuffer = new TimeLimitedInputEventBuffer<>(MAX_AGE_MS);
}
@After
public void tearDown() {
- for (MotionEvent motionEvent : mBuffer) {
- motionEvent.recycle();
+ for (InputEvent inputEvent : mBuffer) {
+ inputEvent.recycle();
}
mBuffer.clear();
}
@Test
- public void testAllEventsRetained() {
+ public void testMotionEventAllEventsRetained() {
MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
@@ -70,7 +72,7 @@ public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
}
@Test
- public void testOlderEventsRemoved() {
+ public void testMotionEventOlderEventsRemoved() {
MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
MotionEvent eventC = MotionEvent.obtain(
@@ -89,4 +91,40 @@ public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
assertThat(mBuffer.get(0), is(eventC));
assertThat(mBuffer.get(1), is(eventD));
}
+
+ @Test
+ public void testKeyEventAllEventsRetained() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 3, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+
+ assertThat(mBuffer.size(), is(2));
+ }
+
+ @Test
+ public void testKeyEventOlderEventsRemoved() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 1, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+ KeyEvent eventC = KeyEvent.obtain(0, MAX_AGE_MS + 1, MotionEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_A, 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventD = KeyEvent.obtain(0, MAX_AGE_MS + 2, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ assertThat(mBuffer.size(), is(2));
+
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+ assertThat(mBuffer.size(), is(2));
+
+ assertThat(mBuffer.get(0), is(eventC));
+ assertThat(mBuffer.get(1), is(eventD));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 9b8cf592d496..6a0462b72544 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -122,7 +122,6 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
deviceEntryFingerprintAuthRepository.setIsRunning(true)
- deviceEntryRepository.setUnlocked(false)
// Lockscreen
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 7311f4a5ef71..e7caf000ef67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -41,6 +41,8 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.After;
@@ -69,6 +71,7 @@ public class DozeUiTest extends SysuiTestCase {
private Handler mHandler;
private HandlerThread mHandlerThread;
private DozeUi mDozeUi;
+ private FakeExecutor mFakeExecutor;
@Before
public void setUp() throws Exception {
@@ -80,9 +83,9 @@ public class DozeUiTest extends SysuiTestCase {
mHandlerThread.start();
mWakeLock = new WakeLockFake();
mHandler = mHandlerThread.getThreadHandler();
-
+ mFakeExecutor = new FakeExecutor(new FakeSystemClock());
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mDozeLog);
+ mDozeParameters, mFakeExecutor, mDozeLog);
mDozeUi.setDozeMachine(mMachine);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 1c6f25147a1e..a127631a536d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -32,6 +32,7 @@ import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.GestureDetector;
+import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -83,11 +84,14 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
private final GestureDetector.OnGestureListener mGestureListener;
private final DisplayHelper mDisplayHelper;
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
private final Rect mDisplayBounds = Mockito.mock(Rect.class);
+ private final IWindowManager mIWindowManager;
Environment(Set<DreamTouchHandler> handlers) {
mLifecycle = Mockito.mock(Lifecycle.class);
mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
+ mIWindowManager = Mockito.mock(IWindowManager.class);
mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
@@ -100,8 +104,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
mDisplayHelper = Mockito.mock(DisplayHelper.class);
when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
.thenReturn(mDisplayBounds);
- mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory,
- mDisplayHelper, handlers);
+ mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+ mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
mMonitor.init();
final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
@@ -163,7 +167,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
environment.publishInputEvent(initialEvent);
// Verify display bounds passed into TouchHandler#getTouchInitiationRegion
- verify(touchHandler).getTouchInitiationRegion(eq(environment.getDisplayBounds()), any());
+ verify(touchHandler).getTouchInitiationRegion(
+ eq(environment.getDisplayBounds()), any(), any());
final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -182,7 +187,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
return null;
- }).when(touchHandler).getTouchInitiationRegion(any(), any());
+ }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -211,7 +216,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
return null;
- }).when(touchHandler).getTouchInitiationRegion(any(), any());
+ }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -264,7 +269,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
// Make sure there is no active session.
verify(touchHandler, never()).onSessionStart(any());
- verify(touchHandler, never()).getTouchInitiationRegion(any(), any());
+ verify(touchHandler, never()).getTouchInitiationRegion(any(), any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 3b4f683124e8..9266af452abd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -36,16 +36,20 @@ import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -69,6 +73,8 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val biometricSettingsRepository = FakeBiometricSettingsRepository()
private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
@@ -78,11 +84,11 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
private lateinit var underTest: DeviceEntrySideFpsOverlayInteractor
- private val testScope = TestScope(StandardTestDispatcher())
-
@Before
fun setup() {
mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+ kosmos.fakeSceneContainerFlags.enabled = false
+
primaryBouncerInteractor =
PrimaryBouncerInteractor(
bouncerRepository,
@@ -100,6 +106,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
mSelectedUserInteractor,
faceAuthInteractor
)
+
alternateBouncerInteractor =
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),
@@ -114,11 +121,14 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
{ mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
+
underTest =
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
+ kosmos.fakeSceneContainerFlags,
+ kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
keyguardUpdateMonitor
@@ -138,7 +148,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
fpsDetectionRunning = true,
isUnlockingWithFpAllowed = true
)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+ assertThat(showIndicatorForDeviceEntry).isTrue()
}
@Test
@@ -154,7 +164,63 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
fpsDetectionRunning = true,
isUnlockingWithFpAllowed = true
)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+ assertThat(showIndicatorForDeviceEntry).isFalse()
+ }
+
+ @Test
+ fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() =
+ testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest =
+ DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
+ mContext,
+ deviceEntryFingerprintAuthRepository,
+ kosmos.fakeSceneContainerFlags,
+ kosmos.sceneInteractor,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ keyguardUpdateMonitor
+ )
+
+ val showIndicatorForDeviceEntry by
+ collectLastValue(underTest.showIndicatorForDeviceEntry)
+ runCurrent()
+
+ updateBouncerScene(
+ isActive = true,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = true
+ )
+ assertThat(showIndicatorForDeviceEntry).isTrue()
+ }
+
+ @Test
+ fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() =
+ testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest =
+ DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
+ mContext,
+ deviceEntryFingerprintAuthRepository,
+ kosmos.fakeSceneContainerFlags,
+ kosmos.sceneInteractor,
+ primaryBouncerInteractor,
+ alternateBouncerInteractor,
+ keyguardUpdateMonitor
+ )
+
+ val showIndicatorForDeviceEntry by
+ collectLastValue(underTest.showIndicatorForDeviceEntry)
+ runCurrent()
+
+ updateBouncerScene(
+ isActive = false,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = true
+ )
+ assertThat(showIndicatorForDeviceEntry).isFalse()
}
@Test
@@ -170,7 +236,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
fpsDetectionRunning = false,
isUnlockingWithFpAllowed = true
)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+ assertThat(showIndicatorForDeviceEntry).isFalse()
}
}
@@ -187,7 +253,39 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
fpsDetectionRunning = true,
isUnlockingWithFpAllowed = false
)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+ assertThat(showIndicatorForDeviceEntry).isFalse()
+ }
+ }
+
+ @Test
+ fun updatesShowIndicatorForDeviceEntry_fromBouncerScene_whenFpsDetectionNotRunning() {
+ testScope.runTest {
+ val showIndicatorForDeviceEntry by
+ collectLastValue(underTest.showIndicatorForDeviceEntry)
+ runCurrent()
+
+ updateBouncerScene(
+ isActive = true,
+ fpsDetectionRunning = false,
+ isUnlockingWithFpAllowed = true
+ )
+ assertThat(showIndicatorForDeviceEntry).isFalse()
+ }
+ }
+
+ @Test
+ fun updatesShowIndicatorForDeviceEntry_fromBouncerScene_onUnlockingWithFpDisallowed() {
+ testScope.runTest {
+ val showIndicatorForDeviceEntry by
+ collectLastValue(underTest.showIndicatorForDeviceEntry)
+ runCurrent()
+
+ updateBouncerScene(
+ isActive = true,
+ fpsDetectionRunning = true,
+ isUnlockingWithFpAllowed = false
+ )
+ assertThat(showIndicatorForDeviceEntry).isFalse()
}
}
@@ -204,7 +302,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
fpsDetectionRunning = true,
isUnlockingWithFpAllowed = true
)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+ assertThat(showIndicatorForDeviceEntry).isFalse()
}
}
@@ -216,10 +314,10 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
runCurrent()
bouncerRepository.setAlternateVisible(true)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+ assertThat(showIndicatorForDeviceEntry).isTrue()
bouncerRepository.setAlternateVisible(false)
- assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+ assertThat(showIndicatorForDeviceEntry).isFalse()
}
@Test
@@ -266,4 +364,25 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
true
)
}
+
+ private fun TestScope.updateBouncerScene(
+ isActive: Boolean,
+ fpsDetectionRunning: Boolean,
+ isUnlockingWithFpAllowed: Boolean,
+ ) {
+ kosmos.sceneInteractor.changeScene(
+ if (isActive) Scenes.Bouncer else Scenes.Lockscreen,
+ "reason"
+ )
+
+ whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+ .thenReturn(fpsDetectionRunning)
+ whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+ .thenReturn(isUnlockingWithFpAllowed)
+ mContext.orCreateTestableResources.addOverride(
+ R.bool.config_show_sidefps_hint_on_bouncer,
+ true
+ )
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index aa7f9a99c758..c47f0bcb3192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -23,13 +23,15 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.Utils
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -51,12 +53,13 @@ import org.mockito.MockitoAnnotations
class ClockSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
- @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
private val bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(VISIBLE)
private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
private val isAodIconsVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ private val shadeMode: MutableStateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
private lateinit var underTest: ClockSection
@@ -68,7 +71,7 @@ class ClockSectionTest : SysuiTestCase() {
Utils.getStatusBarHeaderHeightKeyguard(context)
private val LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_padding_top
) +
@@ -114,14 +117,15 @@ class ClockSectionTest : SysuiTestCase() {
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
whenever(keyguardClockViewModel.isAodIconsVisible).thenReturn(isAodIconsVisible)
+ whenever(shadeInteractor.shadeMode).thenReturn(shadeMode)
+ whenever(keyguardClockViewModel.shadeInteractor).thenReturn(shadeInteractor)
whenever(smartspaceViewModel.bcSmartspaceVisibility).thenReturn(bcSmartspaceVisibility)
underTest =
ClockSection(
keyguardClockInteractor,
keyguardClockViewModel,
- mContext,
- splitShadeStateController,
+ context,
smartspaceViewModel,
blueprintInteractor
)
@@ -138,7 +142,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -152,7 +156,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -167,7 +171,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -182,7 +186,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -196,7 +200,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -209,7 +213,7 @@ class ClockSectionTest : SysuiTestCase() {
assertLargeClockTop(cs, expectedLargeClockTopMargin)
val expectedSmallClockTopMargin = SMALL_CLOCK_TOP_NON_SPLIT_SHADE
- assertSmallClockTop(cs, expectedSmallClockTopMargin)
+ assertSmallClockTop(cs)
}
@Test
@@ -251,8 +255,11 @@ class ClockSectionTest : SysuiTestCase() {
}
private fun setSplitShade(isInSplitShade: Boolean) {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(context.resources))
- .thenReturn(isInSplitShade)
+ if (isInSplitShade) {
+ shadeMode.value = ShadeMode.Split
+ } else {
+ shadeMode.value = ShadeMode.Single
+ }
}
private fun assertLargeClockTop(cs: ConstraintSet, expectedLargeClockTopMargin: Int) {
@@ -261,11 +268,9 @@ class ClockSectionTest : SysuiTestCase() {
assertThat(largeClockConstraint.layout.topMargin).isEqualTo(expectedLargeClockTopMargin)
}
- private fun assertSmallClockTop(cs: ConstraintSet, expectedSmallClockTopMargin: Int) {
+ private fun assertSmallClockTop(cs: ConstraintSet) {
val smallClockGuidelineConstraint = cs.getConstraint(R.id.small_clock_guideline_top)
assertThat(smallClockGuidelineConstraint.layout.topToTop).isEqualTo(-1)
- assertThat(smallClockGuidelineConstraint.layout.guideBegin)
- .isEqualTo(expectedSmallClockTopMargin)
val smallClockConstraint = cs.getConstraint(R.id.lockscreen_clock_view)
assertThat(smallClockConstraint.layout.topToBottom)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 4f2b690f9fcd..b5f668cef08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -85,6 +86,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
{ mock(VibratorHelper::class.java) },
+ mock(CoroutineDispatcher::class.java),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 0322301b3d6b..7b5dd1fc6c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -30,14 +30,16 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
-import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.Utils
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -67,12 +69,12 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
private lateinit var keyguardClockInteractor: KeyguardClockInteractor
private lateinit var keyguardClockRepository: KeyguardClockRepository
private lateinit var fakeSettings: FakeSettings
+ private val shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
@Mock private lateinit var clockRegistry: ClockRegistry
@Mock private lateinit var clock: ClockController
@Mock private lateinit var largeClock: ClockFaceController
@Mock private lateinit var clockFaceConfig: ClockFaceConfig
@Mock private lateinit var eventController: ClockEventController
- @Mock private lateinit var splitShadeStateController: SplitShadeStateController
@Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor
@Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean>
@Mock private lateinit var shadeInteractor: ShadeInteractor
@@ -100,7 +102,7 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository)
whenever(notifsKeyguardInteractor.areNotificationsFullyHidden)
.thenReturn(areNotificationsFullyHidden)
- whenever(shadeInteractor.shadeMode).thenReturn(MutableStateFlow(ShadeMode.Single))
+ whenever(shadeInteractor.shadeMode).thenReturn(shadeMode)
underTest =
KeyguardClockViewModel(
keyguardInteractor,
@@ -153,6 +155,44 @@ class KeyguardClockViewModelTest : SysuiTestCase() {
assertThat(value()).isEqualTo(false)
}
+ @Test
+ fun testSmallClockTop_splitshade() =
+ scope.runTest {
+ shadeMode.value = ShadeMode.Split
+ if (!ComposeLockscreen.isEnabled) {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_split_shade_top_margin
+ )
+ )
+ } else {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyguard_split_shade_top_margin
+ ) - Utils.getStatusBarHeaderHeightKeyguard(context)
+ )
+ }
+ }
+
+ @Test
+ fun testSmallClockTop_nonSplitshade() =
+ scope.runTest {
+ if (!ComposeLockscreen.isEnabled) {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ )
+ } else {
+ assertThat(underTest.getSmallClockTopMargin(context))
+ .isEqualTo(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+ )
+ }
+ }
+
private fun setupMockClock() {
whenever(clock.largeClock).thenReturn(largeClock)
whenever(largeClock.config).thenReturn(clockFaceConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 695d3b2c6c78..ca403e0addec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.media.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -47,6 +48,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -127,6 +129,12 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
+ // Using a fake package will cause routing operations to fail, so we intercept
+ // scanning-related operations.
+ mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
+ doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
+ doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 44798ea99bee..8b79fa45b8ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -257,6 +257,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
userId = userId,
colorBackground = 0,
isForegroundTask = isForegroundTask,
+ userType = RecentTask.UserType.STANDARD,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
index 9b346d0120ef..fa1c8f8ea2c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
@@ -20,15 +20,10 @@ import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
-import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.PackageManagerWrapper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -40,20 +35,17 @@ import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
-class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() {
- private val iconFactory: IconFactory = mock()
private val packageManagerWrapper: PackageManagerWrapper = mock()
private val packageManager: PackageManager = mock()
private val dispatcher = Dispatchers.Unconfined
private val appIconLoader =
- IconLoaderLibAppIconLoader(
+ BasicPackageManagerAppIconLoader(
backgroundDispatcher = dispatcher,
- context = context,
packageManagerWrapper = packageManagerWrapper,
packageManager = packageManager,
- iconFactoryProvider = { iconFactory }
)
@Test
@@ -70,12 +62,7 @@ class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
val activityInfo = mock<ActivityInfo>()
whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
- val rawIcon = mock<Drawable>()
- whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
-
- val bitmapInfo = mock<BitmapInfo>()
- whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
- whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+ whenever(activityInfo.loadIcon(packageManager)).thenReturn(icon)
}
private fun createIcon(): FastBitmapDrawable =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index b593def283ae..dd621129ad9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -1,9 +1,15 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RecentTaskInfo
+import android.content.pm.UserInfo
+import android.os.UserManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.PRIVATE
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.STANDARD
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.WORK
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -17,6 +23,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -25,12 +32,16 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
private val userTracker: UserTracker = mock()
+ private val userManager: UserManager = mock {
+ whenever(getUserInfo(anyInt())).thenReturn(mock())
+ }
private val recentTaskListProvider =
ShellRecentTaskListProvider(
dispatcher,
Runnable::run,
Optional.of(recentTasks),
- userTracker
+ userTracker,
+ userManager,
)
@Test
@@ -147,6 +158,22 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
.inOrder()
}
+ @Test
+ fun loadRecentTasks_assignsCorrectUserType() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1, userId = 10, userType = STANDARD),
+ createSingleTask(taskId = 2, userId = 20, userType = WORK),
+ createSingleTask(taskId = 3, userId = 30, userType = CLONED),
+ createSingleTask(taskId = 4, userId = 40, userType = PRIVATE),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result.map { it.userType })
+ .containsExactly(STANDARD, WORK, CLONED, PRIVATE)
+ .inOrder()
+ }
+
@Suppress("UNCHECKED_CAST")
private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -155,7 +182,10 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
}
}
- private fun createRecentTask(taskId: Int): RecentTask =
+ private fun createRecentTask(
+ taskId: Int,
+ userType: RecentTask.UserType = STANDARD
+ ): RecentTask =
RecentTask(
taskId = taskId,
displayId = 0,
@@ -164,25 +194,42 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
baseIntentComponent = null,
colorBackground = null,
isForegroundTask = false,
+ userType = userType,
)
- private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
+ private fun createSingleTask(
+ taskId: Int,
+ userId: Int = 0,
+ isVisible: Boolean = false,
+ userType: RecentTask.UserType = STANDARD,
+ ): GroupedRecentTaskInfo {
+ val userInfo =
+ mock<UserInfo> {
+ whenever(isCloneProfile).thenReturn(userType == CLONED)
+ whenever(isManagedProfile).thenReturn(userType == WORK)
+ whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
+ }
+ whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
+ return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+ }
private fun createTaskPair(
taskId1: Int,
+ userId1: Int = 0,
taskId2: Int,
+ userId2: Int = 0,
isVisible: Boolean = false
): GroupedRecentTaskInfo =
GroupedRecentTaskInfo.forSplitTasks(
- createTaskInfo(taskId1, isVisible),
- createTaskInfo(taskId2, isVisible),
+ createTaskInfo(taskId1, userId1, isVisible),
+ createTaskInfo(taskId2, userId2, isVisible),
null
)
- private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+ private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
RecentTaskInfo().apply {
this.taskId = taskId
this.isVisible = isVisible
+ this.userId = userId
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index ac4107359dbc..a84008b0353c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -56,7 +56,8 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() {
topActivityComponent = null,
baseIntentComponent = null,
colorBackground = null,
- isForegroundTask = false
+ isForegroundTask = false,
+ userType = RecentTask.UserType.STANDARD,
)
private val taskView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 82ee99a29427..830f08a0c445 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -23,7 +23,7 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index ea2b22c0a746..0ec8552e8595 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -77,7 +77,7 @@ class QSTileViewModelImplTest : SysuiTestCase() {
underTest =
QSTileViewModelImpl(
QSTileConfigTestBuilder.build {
- policy = QSTilePolicy.Restricted("test_restriction")
+ policy = QSTilePolicy.Restricted(listOf("test_restriction"))
},
{ tileUserActionInteractor },
{ tileDataInteractor },
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 74f50dff99ab..b384fe84fa55 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
@@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.telephony.ServiceState;
@@ -176,6 +177,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
+ @Mock
+ private WifiConfiguration mWifiConfiguration;
private FakeFeatureFlags mFlags = new FakeFeatureFlags();
@@ -1037,9 +1040,19 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
}
@Test
+ public void getConfiguratorQrCodeGeneratorIntentOrNull_configurationNull_returnNull() {
+ mFlags.set(Flags.SHARE_WIFI_QS_BUTTON, true);
+ when(mConnectedEntry.canShare()).thenReturn(true);
+ when(mConnectedEntry.getWifiConfiguration()).thenReturn(null);
+ assertThat(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(
+ mConnectedEntry)).isNull();
+ }
+
+ @Test
public void getConfiguratorQrCodeGeneratorIntentOrNull_wifiShareable() {
mFlags.set(Flags.SHARE_WIFI_QS_BUTTON, true);
when(mConnectedEntry.canShare()).thenReturn(true);
+ when(mConnectedEntry.getWifiConfiguration()).thenReturn(mWifiConfiguration);
assertThat(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(
mConnectedEntry)).isNotNull();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 1cfca68cd452..6846c7227d9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -88,13 +88,14 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: SystemUIDialog
private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
+ private var issueRecordingState = IssueRecordingState()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
- whenever(screenCaptureDisabledDialogDelegate.createDialog())
+ whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(screenCaptureDisabledDialog)
whenever(
userFileManager.getSharedPreferences(
@@ -128,6 +129,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
mediaProjectionMetricsLogger,
userFileManager,
screenCaptureDisabledDialogDelegate,
+ issueRecordingState,
) {
latch.countDown()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index b3df12ee2c10..9e559de9f109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -128,7 +128,7 @@ public class RecordingControllerTest extends SysuiTestCase {
);
mFeatureFlags = new FakeFeatureFlags();
- when(mScreenCaptureDisabledDialogDelegate.createDialog())
+ when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(mScreenCaptureDisabledDialog);
when(mScreenRecordDialogFactory.create(any(), any()))
.thenReturn(mScreenRecordDialogDelegate);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 598a0f2d88d0..943245150e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -93,6 +93,7 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
onStartRecordingClicked,
mediaProjectionMetricsLogger,
systemUIDialogFactory,
+ context,
)
dialog = delegate.createDialog()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
new file mode 100644
index 000000000000..5e7d8fb5df02
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.Window
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyBlocking
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActionExecutorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ private val intentExecutor = mock<ActionIntentExecutor>()
+ private val window = mock<Window>()
+ private val view = mock<View>()
+ private val onDismiss = mock<(() -> Unit)>()
+ private val pendingIntent = mock<PendingIntent>()
+
+ private lateinit var actionExecutor: ActionExecutor
+
+ @Test
+ fun startSharedTransition_callsLaunchIntent() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.startSharedTransition(Intent(Intent.ACTION_EDIT), UserHandle.CURRENT, true)
+ scheduler.advanceUntilIdle()
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verifyBlocking(intentExecutor) {
+ launchIntent(capture(intentCaptor), eq(UserHandle.CURRENT), eq(true), any(), any())
+ }
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun sendPendingIntent_dismisses() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.sendPendingIntent(pendingIntent)
+
+ verify(pendingIntent).send(any(Bundle::class.java))
+ verify(onDismiss).invoke()
+ }
+
+ private fun createActionExecutor(): ActionExecutor {
+ return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 0c324706857f..5e53fe16534d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -64,7 +64,7 @@ class ActionIntentExecutorTest : SysuiTestCase() {
val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
val userHandle = myUserHandle()
- actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ actionIntentExecutor.launchIntent(intent, userHandle, false, null, null)
scheduler.advanceUntilIdle()
verify(activityManagerWrapper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
new file mode 100644
index 000000000000..bde821b79469
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Intent
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.clipboardoverlay.EditTextActivity
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
+ private val actionExecutor = mock<ActionExecutor>()
+ private val accessibilityManager = mock<AccessibilityManager>()
+ private val uiEventLogger = mock<UiEventLogger>()
+ private val smartActionsProvider = mock<SmartActionsProvider>()
+
+ private val request = ScreenshotData.forTesting()
+ private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
+
+ private lateinit var viewModel: ScreenshotViewModel
+ private lateinit var actionsProvider: ScreenshotActionsProvider
+
+ @Before
+ fun setUp() {
+ viewModel = ScreenshotViewModel(accessibilityManager)
+ request.userHandle = UserHandle.OWNER
+ }
+
+ @Test
+ fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
+ assertNotNull(viewModel.previewAction.value)
+ viewModel.previewAction.value!!.invoke()
+ verifyNoMoreInteractions(actionExecutor)
+ }
+
+ @Test
+ fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
+ assertThat(viewModel.actions.value.size).isEqualTo(2)
+ val firstAction = viewModel.actions.value[0]
+ assertThat(firstAction.onClicked).isNotNull()
+ val secondAction = viewModel.actions.value[1]
+ assertThat(secondAction.onClicked).isNotNull()
+ firstAction.onClicked!!.invoke()
+ secondAction.onClicked!!.invoke()
+ verifyNoMoreInteractions(actionExecutor)
+ }
+
+ @Test
+ fun actionAccessed_withResult_launchesIntent() = runTest {
+ actionsProvider = createActionsProvider()
+
+ actionsProvider.setCompletedScreenshot(validResult)
+ viewModel.actions.value[0].onClicked!!.invoke()
+
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
+ actionsProvider = createActionsProvider()
+
+ viewModel.actions.value[0].onClicked!!.invoke()
+ viewModel.previewAction.value!!.invoke()
+ viewModel.actions.value[1].onClicked!!.invoke()
+ actionsProvider.setCompletedScreenshot(validResult)
+
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
+ }
+
+ @Test
+ fun quickShareTapped_wrapsAndSendsIntent() = runTest {
+ val quickShare =
+ Notification.Action(
+ R.drawable.ic_screenshot_edit,
+ "TestQuickShare",
+ PendingIntent.getActivity(
+ context,
+ 0,
+ Intent(context, EditTextActivity::class.java),
+ PendingIntent.FLAG_MUTABLE
+ )
+ )
+ whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then {
+ (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
+ }
+ whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
+ (it.getArgument(0) as Notification.Action).actionIntent
+ }
+ actionsProvider = createActionsProvider()
+
+ viewModel.actions.value[2].onClicked?.invoke()
+ verify(uiEventLogger, never())
+ .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any())
+ verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any())
+ actionsProvider.setCompletedScreenshot(validResult)
+ verify(smartActionsProvider)
+ .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid"))
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq(""))
+ }
+
+ private fun createActionsProvider(): ScreenshotActionsProvider {
+ return DefaultScreenshotActionsProvider(
+ context,
+ viewModel,
+ smartActionsProvider,
+ uiEventLogger,
+ request,
+ "testid",
+ actionExecutor
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 25ba09a0ce90..6a22d8648d91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -84,7 +84,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(mirrorController.toggleSlider).thenReturn(mirror)
+ whenever(mirrorController.getToggleSlider()).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
@@ -129,7 +129,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
@Test
fun testNullMirrorNotTrackingTouch() {
- whenever(mirrorController.toggleSlider).thenReturn(null)
+ whenever(mirrorController.getToggleSlider()).thenReturn(null)
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 02f2e16b9570..cf7c6f4e2174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
import static com.google.common.truth.Truth.assertThat;
@@ -436,6 +437,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue();
+ assertThat(
+ (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING)
+ != 0)
+ .isTrue();
}
@Test
@@ -444,6 +449,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
+ assertThat(
+ (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING)
+ == 0)
+ .isTrue();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index dfbb699ed915..2c0a15dd4e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -69,7 +69,6 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -87,6 +86,8 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
+import java.util.Optional
import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -138,6 +139,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
private val notificationLaunchAnimationInteractor =
NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
+ private lateinit var falsingCollector: FalsingCollectorFake
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -170,11 +172,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
+ falsingCollector = FalsingCollectorFake()
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
- FalsingCollectorFake(),
+ falsingCollector,
sysuiStatusBarStateController,
dockManager,
notificationShadeDepthController,
@@ -566,6 +569,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
}
+ @Test
+ fun forwardsCollectKeyEvent() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+ interactionEventHandler.collectKeyEvent(keyEvent)
+ assertEquals(keyEvent, falsingCollector.lastKeyEvent)
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 722387c79536..02954b805ad1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -28,7 +28,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
-import androidx.test.filters.FlakyTest;
+
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
@@ -46,11 +46,10 @@ import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
-@FlakyTest(bugId = 327655994) // Also b/324682425
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PluginInstanceTest extends SysuiTestCase {
@@ -177,7 +176,7 @@ public class PluginInstanceTest extends SysuiTestCase {
}
@Test
- public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+ public void testLoadUnloadSimultaneous_HoldsUnload() throws Throwable {
final Semaphore loadLock = new Semaphore(1);
final Semaphore unloadLock = new Semaphore(1);
@@ -190,16 +189,16 @@ public class PluginInstanceTest extends SysuiTestCase {
Thread.yield();
boolean isLocked = getLock(unloadLock, 1000);
- // Ensure the bg thread failed to do delete the plugin
+ // Ensure the bg thread failed to delete the plugin
assertNotNull(mPluginInstance.getPlugin());
// We expect that bgThread deadlocked holding the semaphore
assertFalse(isLocked);
};
- AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+ AtomicReference<Throwable> bgFailure = new AtomicReference<Throwable>(null);
Thread bgThread = new Thread(() -> {
assertTrue(getLock(unloadLock, 10));
- assertTrue(getLock(loadLock, 4000)); // Wait for the foreground thread
+ assertTrue(getLock(loadLock, 10000)); // Wait for the foreground thread
assertNotNull(mPluginInstance.getPlugin());
// Attempt to delete the plugin, this should block until the load completes
mPluginInstance.unloadPlugin();
@@ -210,8 +209,9 @@ public class PluginInstanceTest extends SysuiTestCase {
// This protects the test suite from crashing due to the uncaught exception.
bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
- Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
- isBgThreadFailed.set(true);
+ Log.e("PluginInstanceTest#testLoadUnloadSimultaneous_HoldsUnload",
+ "Exception from BG Thread", ex);
+ bgFailure.set(ex);
});
loadLock.acquire();
@@ -222,7 +222,13 @@ public class PluginInstanceTest extends SysuiTestCase {
mPluginInstance.loadPlugin();
bgThread.join(5000);
- assertFalse(isBgThreadFailed.get());
+
+ // Rethrow final background exception on test thread
+ Throwable bgEx = bgFailure.get();
+ if (bgEx != null) {
+ throw bgEx;
+ }
+
assertNull(mPluginInstance.getPlugin());
}
@@ -230,6 +236,8 @@ public class PluginInstanceTest extends SysuiTestCase {
try {
return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
+ Log.e("PluginInstanceTest#getLock",
+ "Interrupted Exception getting lock", ex);
fail();
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e54b53225320..de6108632153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -42,6 +41,7 @@ import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -50,6 +50,7 @@ import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionI
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
@@ -309,17 +310,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
underTest.addCallback(listener)
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
+ assertThat(deviceUnlockStatus!!.isUnlocked).isFalse()
+
kosmos.sceneInteractor.changeScene(
toScene = Scenes.Lockscreen,
loggingReason = "reason"
)
runCurrent()
- assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
// Call start to begin hydrating based on the scene framework:
@@ -371,14 +375,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
underTest.addCallback(listener)
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
+
+ assertThat(deviceUnlockStatus!!.isUnlocked).isTrue()
+
kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
runCurrent()
- assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue()
assertThat(currentScene).isEqualTo(Scenes.Gone)
// Call start to begin hydrating based on the scene framework:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index cac4a8d2d37f..6bda4d4fd48b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -299,8 +299,6 @@ public class FooterViewTest extends SysuiTestCase {
@Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
- assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
- assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
.isEqualTo(View.VISIBLE);
}
@@ -308,8 +306,6 @@ public class FooterViewTest extends SysuiTestCase {
@Test
public void testSetFooterLabelInvisible() {
mView.setFooterLabelVisible(false);
- assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
.isEqualTo(View.GONE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 620d97275949..158f38d48197 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -66,7 +66,7 @@ class FooterViewModelTest : SysuiTestCase() {
val underTest = kosmos.footerViewModel
@Test
- fun testMessageVisible_whenFilteredNotifications() =
+ fun messageVisible_whenFilteredNotifications() =
testScope.runTest {
val visible by collectLastValue(underTest.message.isVisible)
@@ -76,7 +76,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testMessageVisible_whenNoFilteredNotifications() =
+ fun messageVisible_whenNoFilteredNotifications() =
testScope.runTest {
val visible by collectLastValue(underTest.message.isVisible)
@@ -86,7 +86,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testClearAllButtonVisible_whenHasClearableNotifs() =
+ fun clearAllButtonVisible_whenHasClearableNotifs() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
@@ -104,7 +104,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
+ fun clearAllButtonVisible_whenHasNoClearableNotifs() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
@@ -122,7 +122,26 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
+ fun clearAllButtonVisible_whenMessageVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+ runCurrent()
+
+ assertThat(visible?.value).isFalse()
+ }
+
+ @Test
+ fun clearAllButtonAnimating_whenShadeExpandedAndTouchable() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
@@ -156,7 +175,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testClearAllButtonAnimating_whenShadeNotExpanded() =
+ fun clearAllButtonAnimating_whenShadeNotExpanded() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
@@ -190,7 +209,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testManageButton_whenHistoryDisabled() =
+ fun manageButton_whenHistoryDisabled() =
testScope.runTest {
val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
runCurrent()
@@ -203,7 +222,7 @@ class FooterViewModelTest : SysuiTestCase() {
}
@Test
- fun testHistoryButton_whenHistoryEnabled() =
+ fun historyButton_whenHistoryEnabled() =
testScope.runTest {
val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
runCurrent()
@@ -214,4 +233,24 @@ class FooterViewModelTest : SysuiTestCase() {
// THEN label is "History"
assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
}
+
+ @Test
+ fun manageButtonVisible_whenMessageVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.manageOrHistoryButton.isVisible)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+
+ assertThat(visible?.value).isFalse()
+ }
+
+ @Test
+ fun manageButtonVisible_whenMessageNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.manageOrHistoryButton.isVisible)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(visible?.value).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33a838ed5183..4b0b4b89fad4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -25,6 +25,7 @@ import android.testing.AndroidTestingRunner
import android.util.StatsEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertLogsWtf
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -133,8 +134,10 @@ class NotificationMemoryLoggerTest : SysuiTestCase() {
val pipeline: NotifPipeline = mock()
whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
- assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
- .isEqualTo(StatsManager.PULL_SKIP)
+ assertLogsWtf {
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index cd8be573a2ac..912ecb340c3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -94,7 +94,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -161,7 +160,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private Provider<WindowRootView> mWindowRootView;
- @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger(
@@ -1016,7 +1014,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mViewBinder,
mShadeController,
mWindowRootView,
- mNotificationStackAppearanceInteractor,
mKosmos.getInteractionJankMonitor(),
mStackLogger,
mLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ed29665c6e16..2f153d8b7003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -25,6 +25,8 @@ import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -119,9 +121,9 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -331,7 +333,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
- private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+ private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
+ mKosmos.getBrightnessMirrorShowingInteractor();
@Before
public void setup() throws Exception {
@@ -553,7 +558,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags
+ mSceneContainerFlags,
+ mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
@@ -1084,6 +1090,34 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
verify(mStatusBarWindowController).refreshStatusBarHeight();
}
+ @Test
+ public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(true);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
+ mTestScope.getTestScheduler().runCurrent();
+ ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
+ // The default is to call the one with the callback argument
+ verify(mScrimController).transitionTo(captor.capture(), any());
+ assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
+ }
+
+ @Test
+ public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(false);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index c1ef1ad9eef9..3e9006e5268c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -46,6 +47,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
@@ -157,6 +159,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
public void testHeaderUpdated() {
mRow.setPinned(true);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 3c13906dbd43..c13e830afac7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.net.ConnectivityManager
-import android.os.PersistableBundle
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -100,9 +99,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
)
)
- // Use a real config, with no overrides
- private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_ID, PersistableBundle())
-
private lateinit var mobileRepo: FakeMobileConnectionRepository
private lateinit var carrierMergedRepo: FakeMobileConnectionRepository
@@ -684,6 +680,10 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
telephonyManager: TelephonyManager,
): MobileConnectionRepositoryImpl {
whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+ val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock()
+ whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds)
+ .thenReturn(MutableStateFlow(0))
+
val realRepo =
MobileConnectionRepositoryImpl(
SUB_ID,
@@ -693,7 +693,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
SEP,
connectivityManager,
telephonyManager,
- systemUiCarrierConfig = systemUiCarrierConfig,
+ systemUiCarrierConfig = systemUiCarrierConfigMock,
fakeBroadcastDispatcher,
mobileMappingsProxy = mock(),
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 9d1411625a8f..f761bcfe63d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -1030,26 +1030,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- fun inflateSignalStrength_usesCarrierConfig() =
- testScope.runTest {
- val latest by collectLastValue(underTest.inflateSignalStrength)
-
- assertThat(latest).isEqualTo(false)
-
- systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
- )
-
- assertThat(latest).isEqualTo(true)
-
- systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
- )
-
- assertThat(latest).isEqualTo(false)
- }
-
- @Test
fun isAllowedDuringAirplaneMode_alwaysFalse() =
testScope.runTest {
val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index f9ab25e9306a..c49fcf88ecaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -181,22 +181,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
}
@Test
- fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() =
- testScope.runTest {
- connectionRepository.inflateSignalStrength.value = false
- val latest by collectLastValue(underTest.signalLevelIcon)
-
- connectionRepository.primaryLevel.value = 4
- assertThat(latest!!.level).isEqualTo(4)
-
- connectionRepository.inflateSignalStrength.value = true
- connectionRepository.primaryLevel.value = 4
-
- // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
- assertThat(latest!!.level).isEqualTo(5)
- }
-
- @Test
fun iconGroup_three_g() =
testScope.runTest {
connectionRepository.resolvedNetworkType.value =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 581ca3b14822..4ace163164f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -28,8 +28,10 @@ import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
+import android.os.Process
import android.os.UserHandle
import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
@@ -41,7 +43,8 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
-import com.android.server.notification.Flags
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.RankingBuilder
@@ -77,7 +80,7 @@ import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
-@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+@EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -384,13 +387,33 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() {
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun shouldProtectNotification_projectionActive_isFromCoreApp_false() {
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding
setShareFullScreenViaSystemUi()
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -407,7 +430,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
.thenReturn(PackageManager.PERMISSION_GRANTED)
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertTrue(controller.shouldProtectNotification(notificationEntry))
}
@@ -424,7 +447,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
.thenReturn(PackageManager.PERMISSION_GRANTED)
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -434,7 +457,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
setShareFullScreenViaBugReportHandler()
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -657,6 +680,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
private fun setupNotificationEntry(
packageName: String,
isFgs: Boolean = false,
+ isCoreApp: Boolean = false,
overrideVisibility: Boolean = false,
overrideChannelVisibility: Boolean = false,
): NotificationEntry {
@@ -668,8 +692,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
// Developer has marked notification as public
notification.visibility = VISIBILITY_PUBLIC
}
- val notificationEntry =
- NotificationEntryBuilder().setNotification(notification).setPkg(packageName).build()
+ val notificationEntryBuilder =
+ NotificationEntryBuilder().setNotification(notification).setPkg(packageName)
+ if (isCoreApp) {
+ notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10)
+ } else {
+ notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10)
+ }
+ val notificationEntry = notificationEntryBuilder.build()
val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH)
if (overrideChannelVisibility) {
// User doesn't allow private notifications at the channel level
@@ -688,6 +718,10 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
return setupNotificationEntry(packageName, isFgs = true)
}
+ private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, isCoreApp = true)
+ }
+
private fun setupPublicNotificationEntry(packageName: String): NotificationEntry {
return setupNotificationEntry(packageName, overrideVisibility = true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
new file mode 100644
index 000000000000..dddc71216da1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.os.PowerManager
+import android.os.SystemProperties
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class FoldLightRevealOverlayAnimationTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope: TestScope = kosmos.testScope
+ private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository
+ private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory
+ private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController
+ private val mockFoldLockSettingAvailabilityProvider =
+ mock<FoldLockSettingAvailabilityProvider>()
+ private val onOverlayReady = mock<Runnable>()
+ private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable)
+ .thenReturn(true)
+ fakeAnimationStatusRepository.onAnimationStatusChanged(true)
+
+ foldLightRevealAnimation =
+ FoldLightRevealOverlayAnimation(
+ kosmos.testDispatcher,
+ fakeDeviceStateRepository,
+ powerInteractor,
+ testScope.backgroundScope,
+ fakeAnimationStatusRepository,
+ mockControllerFactory,
+ mockFoldLockSettingAvailabilityProvider
+ )
+ foldLightRevealAnimation.init()
+ }
+
+ @Test
+ fun foldToScreenOn_playFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ turnScreenOn()
+
+ verifyFoldAnimationPlayed()
+ }
+
+ @Test
+ fun foldToAod_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ emitLastWakefulnessEventStartingToSleep()
+ advanceTimeBy(SHORT_DELAY_MS)
+ turnScreenOn()
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun foldToScreenOff_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ emitLastWakefulnessEventStartingToSleep()
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+ fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+
+ verifyOverlayWasRemoved()
+ }
+
+ @Test
+ fun foldToScreenOn_removeOverlayAfterCompletion() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ turnScreenOn()
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyOverlayWasRemoved()
+ }
+
+ @Test
+ fun unfold_immediatelyRunRunnable() =
+ testScope.runTest {
+ foldLightRevealAnimation.onScreenTurningOn(onOverlayReady)
+
+ verify(onOverlayReady).run()
+ }
+
+ private suspend fun TestScope.foldDeviceToScreenOff() {
+ fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ fakeDeviceStateRepository.emit(DeviceState.FOLDED)
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF)
+ advanceTimeBy(SHORT_DELAY_MS)
+ }
+
+ private fun TestScope.turnScreenOn() {
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ }
+
+ private fun emitLastWakefulnessEventStartingToSleep() =
+ powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+
+ private fun verifyFoldAnimationPlayed() =
+ verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any())
+
+ private fun verifyFoldAnimationDidNotPlay() =
+ verify(mockFullScreenController, never()).updateRevealAmount(any())
+
+ private fun verifyOverlayWasRemoved() =
+ verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved()
+
+ private companion object {
+ const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+ val ANIMATION_DURATION: Long
+ get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+ const val SHORT_DELAY_MS = 50L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
index 1125d41856c6..0eba21ada789 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
@@ -16,9 +16,12 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -32,6 +35,7 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -77,6 +81,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private Executor mBackgroundExecutor;
private int mColorsProcessed;
+ private int mLocalColorsProcessed;
private int mMiniBitmapUpdatedCount;
private int mActivatedCount;
private int mDeactivatedCount;
@@ -93,6 +98,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private void resetCounters() {
mColorsProcessed = 0;
+ mLocalColorsProcessed = 0;
mMiniBitmapUpdatedCount = 0;
mActivatedCount = 0;
mDeactivatedCount = 0;
@@ -112,10 +118,14 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
new Object(),
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
+ public void onColorsProcessed() {
+ mColorsProcessed++;
+ }
+ @Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
assertThat(regions.size()).isEqualTo(colors.size());
- mColorsProcessed += regions.size();
+ mLocalColorsProcessed += regions.size();
}
@Override
@@ -148,8 +158,10 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
.when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
- doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ WallpaperColors colors = new WallpaperColors(
+ Color.valueOf(0), Color.valueOf(0), Color.valueOf(0));
+ doReturn(colors).when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ doReturn(colors).when(spyColorExtractor).getWallpaperColors(any(Bitmap.class), anyFloat());
return spyColorExtractor;
}
@@ -244,7 +256,7 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
assertThat(mActivatedCount).isEqualTo(1);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
@@ -329,12 +341,69 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
}
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
+ /**
+ * Test that after the bitmap changes, the colors are computed only if asked via onComputeColors
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColors() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(0);
+ spyColorExtractor.onComputeColors();
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after onComputeColors is called, the colors are computed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onComputeColors();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors are computed if the bitmap is already loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChanged() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onDimAmountChanged(0.5f);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors will be recomputed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChangedBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onDimAmountChanged(0.3f);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
@Test
public void testCleanUp() {
resetCounters();
@@ -346,6 +415,6 @@ public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
spyColorExtractor.cleanUp();
spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
- assertThat(mColorsProcessed).isEqualTo(0);
+ assertThat(mLocalColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 3d84291292c9..65dd411d5f05 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
@@ -85,6 +86,7 @@ data class TestMocksModule(
@get:Provides val activityStarter: ActivityStarter = mock(),
@get:Provides val activityManagerWrapper: ActivityManagerWrapper = mock(),
@get:Provides val ambientState: AmbientState = mock(),
+ @get:Provides val authController: AuthController = mock(),
@get:Provides val bubbles: Optional<Bubbles> = Optional.of(mock()),
@get:Provides val darkIconDispatcher: DarkIconDispatcher = mock(),
@get:Provides val demoModeController: DemoModeController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
index 34a9c8ab7381..d1709d6c5ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -30,5 +30,6 @@ val Kosmos.fingerprintPropertyInteractor by Fixture {
repository = fingerprintPropertyRepository,
configurationInteractor = configurationInteractor,
displayStateInteractor = displayStateInteractor,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt
new file mode 100644
index 000000000000..2e5ddc700d71
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.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.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeBrightnessPolicyRepository by Kosmos.Fixture { FakeBrightnessPolicyRepository() }
+
+var Kosmos.brightnessPolicyRepository: BrightnessPolicyRepository by
+ Kosmos.Fixture { fakeBrightnessPolicyRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
new file mode 100644
index 000000000000..d3ceb15ca5a2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.utils.PolicyRestriction
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBrightnessPolicyRepository : BrightnessPolicyRepository {
+ private val _restrictionPolicy: MutableStateFlow<PolicyRestriction> =
+ MutableStateFlow(PolicyRestriction.NoRestriction)
+ override val restrictionPolicy = _restrictionPolicy.asStateFlow()
+
+ fun setCurrentUserUnrestricted() {
+ _restrictionPolicy.value = PolicyRestriction.NoRestriction
+ }
+
+ fun setCurrentUserRestricted() {
+ _restrictionPolicy.value =
+ PolicyRestriction.Restricted(
+ RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+ BrightnessPolicyRepository.RESTRICTION
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
new file mode 100644
index 000000000000..a05b5e65ce9d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.brightness.data.repository
+
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
+import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+import com.android.systemui.brightness.data.model.LinearBrightness
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeScreenBrightnessRepository(
+ initialBrightnessInfo: BrightnessInfo =
+ BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE)
+) : ScreenBrightnessRepository {
+
+ private val brightnessInfo = MutableStateFlow(initialBrightnessInfo)
+ private val _temporaryBrightness =
+ MutableStateFlow(LinearBrightness(initialBrightnessInfo.brightness))
+ val temporaryBrightness = _temporaryBrightness.asStateFlow()
+ override val linearBrightness = brightnessInfo.map { LinearBrightness(it.brightness) }
+ override val minLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMinimum) }
+ override val maxLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMaximum) }
+
+ override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+ return minMaxLinearBrightness()
+ }
+
+ private fun minMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+ return with(brightnessInfo.value) {
+ LinearBrightness(brightnessMinimum) to LinearBrightness(brightnessMaximum)
+ }
+ }
+
+ override fun setTemporaryBrightness(value: LinearBrightness) {
+ val bounds = minMaxLinearBrightness()
+ val clampedValue = value.clamp(bounds.first, bounds.second)
+ _temporaryBrightness.value = clampedValue
+ }
+
+ override fun setBrightness(value: LinearBrightness) {
+ val bounds = minMaxLinearBrightness()
+ val clampedValue = value.clamp(bounds.first, bounds.second)
+ _temporaryBrightness.value = clampedValue
+ brightnessInfo.value =
+ with(brightnessInfo.value) {
+ BrightnessInfo(
+ clampedValue.floatValue,
+ brightnessMinimum,
+ brightnessMaximum,
+ highBrightnessMode,
+ highBrightnessTransitionPoint,
+ brightnessMaxReason,
+ )
+ }
+ }
+
+ fun setMinMaxBrightness(min: LinearBrightness, max: LinearBrightness) {
+ check(min.floatValue <= max.floatValue)
+ val clampedBrightness = LinearBrightness(brightnessInfo.value.brightness).clamp(min, max)
+ _temporaryBrightness.value = clampedBrightness
+ brightnessInfo.value =
+ with(brightnessInfo.value) {
+ BrightnessInfo(
+ clampedBrightness.floatValue,
+ min.floatValue,
+ max.floatValue,
+ highBrightnessMode,
+ highBrightnessTransitionPoint,
+ brightnessMaxReason
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt
new file mode 100644
index 000000000000..c5b0eb292a67
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeScreenBrightnessRepository: FakeScreenBrightnessRepository by
+ Kosmos.Fixture { FakeScreenBrightnessRepository() }
+
+var Kosmos.screenBrightnessRepository: ScreenBrightnessRepository by
+ Kosmos.Fixture { fakeScreenBrightnessRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt
new file mode 100644
index 000000000000..9d3c8d2bc30a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.domain.interactor
+
+import com.android.systemui.brightness.data.repository.brightnessPolicyRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.brightnessPolicyEnforcementInteractor by
+ Kosmos.Fixture {
+ BrightnessPolicyEnforcementInteractor(brightnessPolicyRepository, activityStarter)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
new file mode 100644
index 000000000000..22784e47d277
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.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.brightness.domain.interactor
+
+import com.android.systemui.brightness.data.repository.screenBrightnessRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.screenBrightnessInteractor by
+ Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastSenderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastSenderKosmos.kt
new file mode 100644
index 000000000000..42ad6797040c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/BroadcastSenderKosmos.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.broadcast
+
+import android.content.applicationContext
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.wakelock.WakeLockFake
+
+val Kosmos.mockBroadcastSender by Kosmos.Fixture { mock<BroadcastSender>() }
+var Kosmos.broadcastSender by
+ Kosmos.Fixture {
+ BroadcastSender(applicationContext, WakeLockFake.Builder(applicationContext), fakeExecutor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 77caeaa6da4d..045bd5d286df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -21,7 +21,6 @@ import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [DeviceEntryRepository] */
@SysUISingleton
@@ -31,21 +30,10 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
- private val _isUnlocked = MutableStateFlow(false)
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override suspend fun isLockscreenEnabled(): Boolean {
return isLockscreenEnabled
}
- override fun reportSuccessfulAuthentication() {
- _isUnlocked.value = true
- }
-
- fun setUnlocked(isUnlocked: Boolean) {
- _isUnlocked.value = isUnlocked
- }
-
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
this.isLockscreenEnabled = isLockscreenEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index e73e2950bbb9..bff10a191d5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -23,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -35,11 +34,10 @@ val Kosmos.deviceEntryInteractor by
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
- trustInteractor = trustInteractor,
- flags = sceneContainerFlags,
- deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ trustInteractor = trustInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index df1cdc2f72cb..14210bc8d15c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,14 +18,20 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.deviceUnlockedInteractor by Fixture {
DeviceUnlockedInteractor(
applicationScope = applicationCoroutineScope,
authenticationInteractor = authenticationInteractor,
deviceEntryRepository = deviceEntryRepository,
+ trustInteractor = trustInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ powerInteractor = powerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index f65c74fcebc8..c1d2ad6e1be3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -22,6 +22,8 @@ import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
+import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -29,12 +31,14 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
* that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
*/
@EnableFlags(
- FLAG_SCENE_CONTAINER,
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_COMPOSE_LOCKSCREEN,
- FLAG_MEDIA_IN_SCENE_CONTAINER,
+ FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ FLAG_MEDIA_IN_SCENE_CONTAINER,
+ FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
+ FLAG_PREDICTIVE_BACK_SYSUI,
+ FLAG_SCENE_CONTAINER,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
index d791e949f853..12165cdc5658 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt
@@ -20,4 +20,4 @@ import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.kosmos.Kosmos
val Kosmos.keyguardClockInteractor by
- Kosmos.Fixture { KeyguardClockInteractor(keyguardClockRepository = keyguardClockRepository) }
+ Kosmos.Fixture { KeyguardClockInteractor(keyguardClockRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index e6651a44236f..f86e9b7216ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -20,6 +20,8 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -30,5 +32,7 @@ var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 1b23296ec4d3..afc8f309f6d2 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
@@ -49,6 +49,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -106,6 +107,7 @@ class KosmosJavaAdapter(
val sharedNotificationContainerInteractor by lazy {
kosmos.sharedNotificationContainerInteractor
}
+ val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
index 372a1961159d..1edd405f4af6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
@@ -17,10 +17,12 @@
package com.android.systemui.media.controls.domain.pipeline.interactor
import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastSender
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.plugins.activityStarter
val Kosmos.mediaRecommendationsInteractor by
Kosmos.Fixture {
@@ -29,5 +31,7 @@ val Kosmos.mediaRecommendationsInteractor by
applicationContext = applicationContext,
repository = mediaFilterRepository,
mediaDataProcessor = mediaDataProcessor,
+ broadcastSender = broadcastSender,
+ activityStarter = activityStarter,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
new file mode 100644
index 000000000000..34a527781979
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
+import com.android.systemui.media.controls.util.mediaUiEventLogger
+
+val Kosmos.mediaRecommendationsViewModel by
+ Kosmos.Fixture {
+ MediaRecommendationsViewModel(
+ applicationContext = applicationContext,
+ backgroundDispatcher = testDispatcher,
+ interactor = mediaRecommendationsInteractor,
+ logger = mediaUiEventLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 394c8733487a..695e59484014 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -24,9 +24,10 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.mockito.mock
-var Kosmos.statusBarStateController by
+var Kosmos.statusBarStateController: SysuiStatusBarStateController by
Kosmos.Fixture {
StatusBarStateControllerImpl(
uiEventLogger,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 1ce26109ed04..0de76c802faf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -35,7 +35,6 @@ import com.android.systemui.plugins.qs.QSFactory
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.foregroundServicesRepository
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.security.data.repository.securityRepository
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.policy.deviceProvisionedController
@@ -49,7 +48,6 @@ val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture {
QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
}
-var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>()
var Kosmos.qsTileFactory by Fixture<QSFactory>()
val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 9ef44c4b9085..b870039982f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -21,7 +21,6 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.qs.external.customTileStatePersister
import com.android.systemui.qs.external.tileLifecycleManagerFactory
-import com.android.systemui.qs.newQSTileFactory
import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
@@ -29,6 +28,7 @@ import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.shared.logging.qsLogger
import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.tiles.di.newQSTileFactory
import com.android.systemui.settings.userTracker
import com.android.systemui.user.data.repository.userRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
index 62765d10486c..fb6ba20e4c51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt
@@ -17,16 +17,21 @@
package com.android.systemui.qs.tiles.base.interactor
import android.os.UserHandle
+import com.android.settingslib.RestrictedLockUtils
class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
- var policyResult: DisabledByPolicyInteractor.PolicyResult =
- DisabledByPolicyInteractor.PolicyResult.TileEnabled
-
override suspend fun isDisabled(
user: UserHandle,
userRestriction: String?
- ): DisabledByPolicyInteractor.PolicyResult = policyResult
+ ): DisabledByPolicyInteractor.PolicyResult =
+ if (userRestriction == DISABLED_RESTRICTION || userRestriction == DISABLED_RESTRICTION_2) {
+ DisabledByPolicyInteractor.PolicyResult.TileDisabled(
+ RestrictedLockUtils.EnforcedAdmin()
+ )
+ } else {
+ DisabledByPolicyInteractor.PolicyResult.TileEnabled
+ }
override fun handlePolicyResult(
policyResult: DisabledByPolicyInteractor.PolicyResult
@@ -35,4 +40,10 @@ class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor {
is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false
is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true
}
+
+ companion object {
+ const val DISABLED_RESTRICTION = "disabled_restriction"
+ const val DISABLED_RESTRICTION_2 = "disabled_restriction_2"
+ const val ENABLED_RESTRICTION = "test_restriction"
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
new file mode 100644
index 000000000000..5c4b39081143
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.mock
+import javax.inject.Provider
+import org.mockito.Mockito
+
+var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
+
+val Kosmos.newQSTileFactory by
+ Kosmos.Fixture {
+ NewQSTileFactory(
+ qSTileConfigProvider,
+ qsTileViewModelAdaperFactory,
+ newFactoryTileMap,
+ mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt
new file mode 100644
index 000000000000..b7e31db2e42f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsCameraSensorPrivacyToggleTileConfig by
+ Kosmos.Fixture { PolicyModule.provideCameraToggleTileConfig(qsEventLogger) }
+
+val Kosmos.qsMicrophoneSensorPrivacyToggleTileConfig by
+ Kosmos.Fixture { PolicyModule.provideMicrophoneToggleTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
new file mode 100644
index 000000000000..1d579797bcb3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.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.qs.tiles.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeQSTileConfigProvider by Kosmos.Fixture { FakeQSTileConfigProvider() }
+var Kosmos.qSTileConfigProvider: QSTileConfigProvider by Kosmos.Fixture { fakeQSTileConfigProvider }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
new file mode 100644
index 000000000000..a90876551d20
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.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.qs.tiles.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileViewModelAdaperFactory by
+ Kosmos.Fixture {
+ object : QSTileViewModelAdapter.Factory {
+ override fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter {
+ return QSTileViewModelAdapter(
+ applicationCoroutineScope,
+ mock(),
+ qsTileViewModel,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index 4d902fa35204..a654d6fc239a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.adapter
import android.content.Context
import android.view.View
+import com.android.systemui.settings.brightness.MirrorController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -41,6 +42,9 @@ class FakeQSSceneAdapter(
private val _navBarPadding = MutableStateFlow<Int>(0)
val navBarPadding = _navBarPadding.asStateFlow()
+ var brightnessMirrorController: MirrorController? = null
+ private set
+
override var isQsFullyCollapsed: Boolean = true
override suspend fun inflate(context: Context) {
@@ -60,4 +64,12 @@ class FakeQSSceneAdapter(
override suspend fun applyBottomNavBarPadding(padding: Int) {
_navBarPadding.value = padding
}
+
+ override fun requestCloseCustomizer() {
+ _customizing.value = false
+ }
+
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ brightnessMirrorController = mirrorController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
new file mode 100644
index 000000000000..8b7e5d8f54c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.util.time.systemClock
+
+/** This factory creates empty mocks. */
+var Kosmos.brightnessSliderControllerFactory by
+ Kosmos.Fixture<BrightnessSliderController.Factory> {
+ BrightnessSliderController.Factory(
+ falsingManager,
+ uiEventLogger,
+ vibratorHelper,
+ systemClock,
+ activityStarter,
+ )
+ }
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
index be1f081d5b8f..6db46499cea9 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
+
+package com.android.systemui.settings.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.brightnessMirrorShowingRepository by
+ Kosmos.Fixture { BrightnessMirrorShowingRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
new file mode 100644
index 000000000000..8f6b829f0021
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.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.settings.brightness.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+
+val Kosmos.brightnessMirrorShowingInteractor by
+ Kosmos.Fixture { BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
new file mode 100644
index 000000000000..8fb370caee09
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightnessSliderControllerFactory
+
+val Kosmos.brightnessMirrorViewModel by
+ Kosmos.Fixture {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 4a2eaf0f7bf6..d08855f190ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -21,6 +21,7 @@ package com.android.systemui.shade
import com.android.systemui.assist.AssistManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -58,6 +59,7 @@ val Kosmos.shadeControllerSceneImpl by
statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
notificationShadeWindowController = mock<NotificationShadeWindowController>(),
assistManagerLazy = { mock<AssistManager>() },
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index bada2a61995d..10cc13697d96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -23,8 +23,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
-val Kosmos.notificationStackAppearanceViewModel by Fixture {
- NotificationStackAppearanceViewModel(
+val Kosmos.notificationScrollViewModel by Fixture {
+ NotificationScrollViewModel(
dumpManager = dumpManager,
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 29faa58d674a..b2492110f849 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif
val Kosmos.notificationsPlaceholderViewModel by Fixture {
NotificationsPlaceholderViewModel(
+ dumpManager = dumpManager,
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
flags = sceneContainerFlags,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index de0cc6590593..d2de835ad954 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -43,6 +43,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +56,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
+ notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 8109b60a9ef0..2d5a3612ff6a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -31,7 +31,6 @@ class FakeMobileConnectionRepository(
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID)
- override val inflateSignalStrength: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isEmergencyOnly = MutableStateFlow(false)
override val isRoaming = MutableStateFlow(false)
override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
new file mode 100644
index 000000000000..43d6c48d4069
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+var Kosmos.fullscreenLightRevealAnimationController by Fixture {
+ mock<FullscreenLightRevealAnimationController>()
+}
+var Kosmos.fullscreenLightRevealAnimationControllerFactory by Fixture {
+ var mockControllerFactory = mock<FullscreenLightRevealAnimationController.Factory>()
+ whenever(mockControllerFactory.create(any(), any(), any()))
+ .thenReturn(fullscreenLightRevealAnimationController)
+ mockControllerFactory
+}
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5e001fba6aa1..5075f6322d1b 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -30,9 +30,6 @@ phony {
"FontNotoSerifSourceOverlay",
"NavigationBarMode3ButtonOverlay",
"NavigationBarModeGesturalOverlay",
- "NavigationBarModeGesturalOverlayNarrowBack",
- "NavigationBarModeGesturalOverlayWideBack",
- "NavigationBarModeGesturalOverlayExtraWideBack",
"TransparentNavigationBarOverlay",
"NotesRoleEnabledOverlay",
"preinstalled-packages-platform-overlays.xml",
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
deleted file mode 100644
index 28f9f3341307..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayExtraWideBack",
- theme: "NavigationBarModeGesturalExtraWideBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
deleted file mode 100644
index ba7bebac16a4..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
deleted file mode 100644
index 120a4893811f..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">40dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e0477f7..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
deleted file mode 100644
index f8a56030e0e4..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayNarrowBack",
- theme: "NavigationBarModeGesturalNarrowBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
deleted file mode 100644
index 8de91c0cf8ab..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_narrow_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081d5b8f..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
deleted file mode 100644
index c18d892ec5b0..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">18dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
deleted file mode 100644
index bbab5e0477f7..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
deleted file mode 100644
index 60ee6d540dc8..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayWideBack",
- theme: "NavigationBarModeGesturalWideBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
deleted file mode 100644
index daf461382b28..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081d5b8f..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2023, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
deleted file mode 100644
index 877b5f82c28e..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">32dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc749bc11..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources> \ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e0477f7..000000000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources> \ No newline at end of file
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index a5b28ad550ba..e77f846ffaa6 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,6 +5,17 @@
},
{
"name": "RavenwoodBivalentTest_device"
+ },
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
],
"ravenwood-presubmit": [
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 5e66b29b370a..83f756e9a0bf 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -42,7 +42,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
ALOGI("%s: JNI_OnLoad", __FILE__);
int res = jniRegisterNativeMethods(env,
- "com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest",
+ "com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
sMethods, NELEM(sMethods));
if (res < 0) {
return res;
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
new file mode 100644
index 000000000000..d91e73438fa3
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.Size;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+// Tests for calling simple Android APIs.
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodAndroidApiTest {
+ @Test
+ public void testArrayMapSimple() {
+ final Map<String, String> map = new ArrayMap<>();
+
+ map.put("key1", "value1");
+ assertEquals("value1", map.get("key1"));
+ }
+
+ @Test
+ public void testSizeSimple() {
+ final var size = new Size(1, 2);
+
+ assertEquals(2, size.getHeight());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
new file mode 100644
index 000000000000..3a24c0e829a4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodClassRuleDeviceOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
new file mode 100644
index 000000000000..aa33dc3392b0
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest;
+
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+// TODO: atest RavenwoodBivalentTest_device fails with the following message.
+// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6`
+// @android.platform.test.annotations.DisabledOnNonRavenwood
+// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well.
+@Ignore
+public class RavenwoodClassRuleRavenwoodOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testRavenwoodOnly() {
+ Assert.assertTrue(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 3b106da74ed4..59467e9d7d14 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import static junit.framework.Assert.assertEquals;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 4b650b4bc5e1..3edca7ea5016 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
index 2cd585ff6c9c..f1e33cb686f1 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.coretest;
+package com.android.ravenwoodtest.coretest;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -40,7 +40,7 @@ public class RavenwoodTestRunnerValidationTest {
public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
public RavenwoodTestRunnerValidationTest() {
- Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+ Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
// Because RavenwoodRule will throw this error before executing the test method,
// we can't do it in the test method itself.
// So instead, we initialize it here.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
index 8ca34bafc2c6..2fb8074c850a 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -31,13 +31,17 @@ import java.lang.annotation.Target;
* which means if a test class has this annotation, you can't negate it in subclasses or
* on a per-method basis.
*
+ * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT.
+ * See {@link com.android.ravenwoodtest.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
+ * for the reason.
+ *
* The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
* propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
*
* @hide
*/
@Inherited
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DisabledOnNonRavenwood {
/**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 9a4d4886d6f0..f4b7ec360dbf 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -25,6 +25,7 @@ import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInP
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -41,27 +42,16 @@ import org.junit.runners.model.Statement;
public class RavenwoodClassRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
- // No special treatment when running outside Ravenwood; run tests as-is
if (!IS_ON_RAVENWOOD) {
- Assume.assumeTrue(shouldEnableOnDevice(description));
- return base;
- }
-
- if (ENABLE_PROBE_IGNORED) {
+ // This should be "Assume", not Assert, but if we use assume here, the device side
+ // test runner would complain.
+ // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest.
+ Assert.assertTrue(shouldEnableOnDevice(description));
+ } else if (ENABLE_PROBE_IGNORED) {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- return base;
} else {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- base.evaluate();
- }
- };
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
}
+ return base;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 52ea3402fa62..21d8019fcd87 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -28,7 +28,6 @@ import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -278,6 +277,12 @@ public class RavenwoodRule implements TestRule {
return false;
}
}
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+ return false;
+ }
+ }
return true;
}
@@ -308,14 +313,17 @@ public class RavenwoodRule implements TestRule {
}
// Otherwise, consult any class-level annotations
- if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
- return true;
- }
- if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
- return false;
- }
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
}
// When no annotations have been requested, assume test should be included
@@ -413,10 +421,9 @@ public class RavenwoodRule implements TestRule {
};
}
- /**
- * Do not use it outside ravenwood core classes.
- */
- public boolean _ravenwood_private$isOptionalValidationEnabled() {
- return ENABLE_OPTIONAL_VALIDATION;
+ public static class _$RavenwoodPrivate {
+ public static boolean isOptionalValidationEnabled() {
+ return ENABLE_OPTIONAL_VALIDATION;
+ }
}
}
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
index d02fe69d3168..d566977bd15c 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
index 0c137d5eaacc..aa2b7611da37 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
index 95667103bd21..fcc6c9cc447d 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
index efe468d0df25..f833782bc8bb 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index c1dee5d2f55b..044239f06297 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
diff --git a/services/Android.bp b/services/Android.bp
index 29d1acf5f350..881d6e12ddc7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -286,6 +286,11 @@ filegroup {
// API stub
// =============================================================
+soong_config_module_type_import {
+ from: "frameworks/base/api/Android.bp",
+ module_types: ["non_updatable_exportable_droidstubs"],
+}
+
stubs_defaults {
name: "services-stubs-default",
installable: false,
@@ -301,10 +306,12 @@ stubs_defaults {
filter_packages: ["com.android."],
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "services-non-updatable-stubs",
srcs: [":services-non-updatable-sources"],
- defaults: ["services-stubs-default"],
+ defaults: [
+ "services-stubs-default",
+ ],
check_api: {
current: {
api_file: "api/current.txt",
@@ -321,14 +328,34 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "system-server",
}
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 467adc7a9628..7a99b605c4fb 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -58,6 +58,7 @@ java_library_static {
aconfig_declarations {
name: "com_android_server_accessibility_flags",
package: "com.android.server.accessibility",
+ container: "system",
srcs: [
"accessibility.aconfig",
],
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 04b19ffd4dfc..1d6399e24b2a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.accessibility"
+container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0811c872d2eb..bbbc4aef55f3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2734,10 +2734,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
+ // Store the list of installed services.
+ mTempComponentNameSet.clear();
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
ComponentName componentName = ComponentName.unflattenFromString(
installedService.getId());
+ mTempComponentNameSet.add(componentName);
AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
@@ -2797,6 +2800,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray);
+
+ // If any services have been removed, remove them from the enabled list and the touch
+ // exploration granted list.
+ boolean anyServiceRemoved =
+ userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp))
+ || userState.mTouchExplorationGrantedServices.removeIf(
+ (comp) -> !mTempComponentNameSet.contains(comp));
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices,
+ userState.mUserId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices,
+ userState.mUserId);
+ }
updateAccessibilityEnabledSettingLocked(userState);
}
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index 2a85eb66f6c5..e13746ed13dd 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -30,5 +30,6 @@ java_library_static {
aconfig_declarations {
name: "backup_flags",
package: "com.android.server.backup",
+ container: "system",
srcs: ["flags.aconfig"],
}
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 74adfe08dba7..d53f949753d8 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.backup"
+container: "system"
flag {
name: "enable_skipping_restore_launched_apps"
@@ -58,4 +59,4 @@ flag {
description: "Increase BMM logging coverage in restore at install flow."
bug: "331749778"
is_fixed_read_only: true
-} \ No newline at end of file
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 8ac1eb9c90b7..30e4a3eb77e6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -52,6 +52,7 @@ import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.ecm.EnhancedConfirmationManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.IAssociationRequestCallback;
@@ -64,6 +65,7 @@ import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -80,6 +82,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.flags.Flags;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -103,10 +106,10 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
-import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
+import com.android.server.companion.devicepresence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -448,15 +451,26 @@ public class CompanionDeviceManagerService extends SystemService {
}
return Binder.withCleanCallingIdentity(() -> {
+ final Intent intent;
if (!isRestrictedSettingsAllowed(getContext(), callingPackage, callingUid)) {
Slog.e(TAG, "Side loaded app must enable restricted "
+ "setting before request the notification access");
- return null;
+ if (Flags.enhancedConfirmationModeApisEnabled()) {
+ intent = getContext()
+ .getSystemService(EnhancedConfirmationManager.class)
+ .createRestrictedSettingDialogIntent(callingPackage,
+ AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+ } else {
+ return null;
+ }
+ } else {
+ intent = NotificationAccessConfirmationActivityContract.launcherIntent(
+ getContext(), userId, component);
}
+
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
- NotificationAccessConfirmationActivityContract.launcherIntent(
- getContext(), userId, component),
+ intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index daa8fdbcab75..9cfb5351f6cf 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -36,8 +36,8 @@ import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
import java.io.PrintWriter;
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index acf683d387a3..8c1116b7a612 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -37,8 +37,8 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
index 407b9daed11c..6cdc02ec67a2 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
@@ -15,27 +15,15 @@
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
-import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.nameForState;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
import static java.util.Objects.requireNonNull;
import android.annotation.MainThread;
@@ -56,21 +44,19 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import android.util.Slog;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.association.AssociationStore.ChangeType;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SuppressLint("LongLogTag")
-class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
- private static final String TAG = "CDM_BleCompanionDeviceScanner";
+class BleDeviceProcessor implements AssociationStore.OnChangeListener {
+ private static final String TAG = "CDM_BleDeviceProcessor";
interface Callback {
void onBleCompanionDeviceFound(int associationId, int userId);
@@ -78,26 +64,27 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
void onBleCompanionDeviceLost(int associationId, int userId);
}
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull Callback mCallback;
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final Callback mCallback;
// Non-null after init().
- private @Nullable BluetoothAdapter mBtAdapter;
+ @Nullable
+ private BluetoothAdapter mBtAdapter;
// Non-null after init() and when BLE is available. Otherwise - null.
- private @Nullable BluetoothLeScanner mBleScanner;
+ @Nullable
+ private BluetoothLeScanner mBleScanner;
// Only accessed from the Main thread.
private boolean mScanning = false;
- BleCompanionDeviceScanner(
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ BleDeviceProcessor(@NonNull AssociationStore associationStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mCallback = callback;
}
@MainThread
void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) {
- if (DEBUG) Log.i(TAG, "init()");
-
if (mBtAdapter != null) {
throw new IllegalStateException(getClass().getSimpleName() + " is already initialized");
}
@@ -113,9 +100,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
final void restartScan() {
enforceInitialized();
- if (DEBUG) Log.i(TAG , "restartScan()");
if (mBleScanner == null) {
- if (DEBUG) Log.d(TAG, " > BLE is not available");
return;
}
@@ -138,12 +123,8 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
enforceInitialized();
final boolean bleAvailable = mBtAdapter.isLeEnabled();
- if (DEBUG) {
- Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
- }
if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) {
// Nothing changed.
- if (DEBUG) Log.i(TAG, " > BLE status did not change");
return;
}
@@ -153,12 +134,9 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
// Oops, that's a race condition. Can return.
return;
}
- if (DEBUG) Log.i(TAG, " > BLE is now available");
startScan();
} else {
- if (DEBUG) Log.i(TAG, " > BLE is now unavailable");
-
stopScanIfNeeded();
mBleScanner = null;
}
@@ -194,13 +172,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
}
}
if (macAddresses.isEmpty()) {
- if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for.");
return;
- } else {
- if (DEBUG) {
- Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")"
- + "[" + String.join(", ", macAddresses) + "]");
- }
}
final List<ScanFilter> filters = new ArrayList<>(macAddresses.size());
@@ -230,7 +202,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
Slog.i(TAG, "stopBleScan()");
if (!mScanning) {
- if (DEBUG) Log.d(TAG, " > not scanning.");
return;
}
// mScanCallback is non-null here - it cannot be null when mScanning is true.
@@ -252,26 +223,16 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
@MainThread
private void notifyDeviceFound(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
-
- for (AssociationInfo association : associations) {
+ for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+ device.getAddress())) {
mCallback.onBleCompanionDeviceFound(association.getId(), association.getUserId());
}
}
@MainThread
private void notifyDeviceLost(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
-
- for (AssociationInfo association : associations) {
+ for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+ device.getAddress())) {
mCallback.onBleCompanionDeviceLost(association.getId(), association.getUserId());
}
}
@@ -280,17 +241,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1);
- final int state = intent.getIntExtra(EXTRA_STATE, -1);
-
- if (DEBUG) {
- // The action is either STATE_CHANGED or BLE_STATE_CHANGED.
- final String action =
- intent.getAction().replace("android.bluetooth.adapter.", "bt.");
- Log.d(TAG, "on(Broadcast)Receive() " + action + ": "
- + nameForBtState(prevState) + "->" + nameForBtState(state));
- }
-
checkBleState();
}
};
@@ -313,16 +263,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
public void onScanResult(int callbackType, ScanResult result) {
final BluetoothDevice device = result.getDevice();
- if (DEBUG) {
- Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType)
- + " device=" + btDeviceToString(device));
- Log.v(TAG, " > scanResult=" + result);
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray()));
- }
-
switch (callbackType) {
case CALLBACK_TYPE_FIRST_MATCH:
notifyDeviceFound(device);
@@ -342,60 +282,20 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
@MainThread
@Override
public void onScanFailed(int errorCode) {
- if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode));
mScanning = false;
}
};
- private static String nameForBtState(int state) {
- return nameForState(state) + "(" + state + ")";
- }
-
private static String nameForBleScanCallbackType(int callbackType) {
- final String name;
- switch (callbackType) {
- case CALLBACK_TYPE_ALL_MATCHES:
- name = "ALL_MATCHES";
- break;
- case CALLBACK_TYPE_FIRST_MATCH:
- name = "FIRST_MATCH";
- break;
- case CALLBACK_TYPE_MATCH_LOST:
- name = "MATCH_LOST";
- break;
- default:
- name = "Unknown";
- }
+ final String name = switch (callbackType) {
+ case CALLBACK_TYPE_ALL_MATCHES -> "ALL_MATCHES";
+ case CALLBACK_TYPE_FIRST_MATCH -> "FIRST_MATCH";
+ case CALLBACK_TYPE_MATCH_LOST -> "MATCH_LOST";
+ default -> "Unknown";
+ };
return name + "(" + callbackType + ")";
}
- private static String nameForBleScanErrorCode(int errorCode) {
- final String name;
- switch (errorCode) {
- case SCAN_FAILED_ALREADY_STARTED:
- name = "ALREADY_STARTED";
- break;
- case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
- name = "APPLICATION_REGISTRATION_FAILED";
- break;
- case SCAN_FAILED_INTERNAL_ERROR:
- name = "INTERNAL_ERROR";
- break;
- case SCAN_FAILED_FEATURE_UNSUPPORTED:
- name = "FEATURE_UNSUPPORTED";
- break;
- case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
- name = "OUT_OF_HARDWARE_RESOURCES";
- break;
- case SCAN_FAILED_SCANNING_TOO_FREQUENTLY:
- name = "SCANNING_TOO_FREQUENTLY";
- break;
- default:
- name = "Unknown";
- }
- return name + "(" + errorCode + ")";
- }
-
private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder()
.setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
.setScanMode(SCAN_MODE_LOW_POWER)
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
index e1a8db41f433..612c156d867e 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
@@ -32,8 +29,6 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.server.companion.association.AssociationStore;
@@ -45,10 +40,10 @@ import java.util.List;
import java.util.Map;
@SuppressLint("LongLogTag")
-public class BluetoothCompanionDeviceConnectionListener
+public class BluetoothDeviceProcessor
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
- private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener";
+ private static final String TAG = "CDM_BluetoothDeviceProcessor";
interface Callback {
void onBluetoothCompanionDeviceConnected(int associationId, int userId);
@@ -58,24 +53,25 @@ public class BluetoothCompanionDeviceConnectionListener
void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull Callback mCallback;
- /** A set of ALL connected BT device (not only companion.) */
- private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final ObservableUuidStore mObservableUuidStore;
+ @NonNull
+ private final Callback mCallback;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
+ /** A set of ALL connected BT device (not only companion.) */
+ @NonNull
+ private final Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
- BluetoothCompanionDeviceConnectionListener(UserManager userManager,
- @NonNull AssociationStore associationStore,
+ BluetoothDeviceProcessor(@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mCallback = callback;
}
- public void init(@NonNull BluetoothAdapter btAdapter) {
- if (DEBUG) Log.i(TAG, "init()");
-
+ void init(@NonNull BluetoothAdapter btAdapter) {
btAdapter.registerBluetoothConnectionCallback(
new HandlerExecutor(Handler.getMain()), /* callback */this);
mAssociationStore.registerLocalListener(this);
@@ -87,13 +83,9 @@ public class BluetoothCompanionDeviceConnectionListener
*/
@Override
public void onDeviceConnected(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
-
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
- final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.put(macAddress, device) != null) {
- if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
return;
}
@@ -108,18 +100,9 @@ public class BluetoothCompanionDeviceConnectionListener
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
int reason) {
- if (DEBUG) {
- Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
- Log.d(TAG, " reason=" + disconnectReasonToString(reason));
- }
-
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
- final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.remove(macAddress) == null) {
- if (DEBUG) {
- Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
- }
return;
}
@@ -130,22 +113,6 @@ public class BluetoothCompanionDeviceConnectionListener
int userId = UserHandle.myUserId();
final List<AssociationInfo> associations =
mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForUser(userId);
- final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
-
- final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
- ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
-
- if (DEBUG) {
- Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
- + " connected=" + connected);
- if (associations.isEmpty()) {
- Log.d(TAG, " > No CDM associations");
- } else {
- Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
- }
- }
for (AssociationInfo association : associations) {
if (!association.isNotifyOnDeviceNearby()) continue;
@@ -157,44 +124,25 @@ public class BluetoothCompanionDeviceConnectionListener
}
}
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+ final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
+
for (ObservableUuid uuid : observableUuids) {
if (deviceUuids.contains(uuid.getUuid())) {
mCallback.onDevicePresenceEventByUuid(uuid, connected ? EVENT_BT_CONNECTED
- : EVENT_BT_DISCONNECTED);
+ : EVENT_BT_DISCONNECTED);
}
}
}
@Override
public void onAssociationAdded(AssociationInfo association) {
- if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
-
if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
mCallback.onBluetoothCompanionDeviceConnected(
association.getId(), association.getUserId());
}
}
-
- @Override
- public void onAssociationRemoved(AssociationInfo association) {
- // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping
- // required.
- }
-
- @Override
- public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
- if (DEBUG) {
- Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged
- + " " + association);
- }
-
- if (!addressChanged) {
- // Don't need to do anything.
- return;
- }
-
- // At the moment CDM does allow changing association addresses, so we will never come here.
- // This will be implemented when CDM support updating addresses.
- throw new IllegalArgumentException("Address changes are not supported.");
- }
}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index b6348ea9594d..60f46887fa5c 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
index c01c3195e04d..5923e70c2300 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index 092886cbfe18..cfb7f337242b 100644
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
@@ -24,6 +24,7 @@ import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
+import static android.content.Context.BLUETOOTH_SERVICE;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -35,6 +36,7 @@ import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
import android.companion.DevicePresenceEvent;
@@ -49,7 +51,6 @@ import android.os.ParcelUuid;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserManager;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -80,8 +81,7 @@ import java.util.Set;
*/
@SuppressLint("LongLogTag")
public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
+ BluetoothDeviceProcessor.Callback, BleDeviceProcessor.Callback {
private static final String TAG = "CDM_DevicePresenceProcessor";
@NonNull
@@ -93,9 +93,9 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull
private final ObservableUuidStore mObservableUuidStore;
@NonNull
- private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ private final BluetoothDeviceProcessor mBluetoothDeviceProcessor;
@NonNull
- private final BleCompanionDeviceScanner mBleScanner;
+ private final BleDeviceProcessor mBleDeviceProcessor;
@NonNull
private final PowerManagerInternal mPowerManagerInternal;
@NonNull
@@ -142,7 +142,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
public DevicePresenceProcessor(@NonNull Context context,
@NonNull CompanionAppBinder companionAppBinder,
- UserManager userManager,
+ @NonNull UserManager userManager,
@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore,
@NonNull PowerManagerInternal powerManagerInternal) {
@@ -151,26 +151,28 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mUserManager = userManager;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
+ mBluetoothDeviceProcessor = new BluetoothDeviceProcessor(associationStore,
+ mObservableUuidStore, this);
+ mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
mPowerManagerInternal = powerManagerInternal;
}
/** Initialize {@link DevicePresenceProcessor} */
public void init(Context context) {
- if (DEBUG) Slog.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
+ BluetoothManager bm = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE);
+ if (bm == null) {
+ Slog.w(TAG, "BluetoothManager is not available.");
+ return;
+ }
+ final BluetoothAdapter btAdapter = bm.getAdapter();
+ if (btAdapter == null) {
Slog.w(TAG, "BluetoothAdapter is NOT available.");
+ return;
}
+ mBluetoothDeviceProcessor.init(btAdapter);
+ mBleDeviceProcessor.init(context, btAdapter);
+
mAssociationStore.registerLocalListener(this);
}
@@ -280,7 +282,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* For legacy device presence below Android V.
*
* @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
+ * int)}
*/
@Deprecated
public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -310,7 +312,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* For legacy device presence below Android V.
*
* @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
+ * int)}
*/
@Deprecated
public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -496,7 +498,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
// Stop the BLE scan if all devices report BT connected status and BLE was present.
if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
+ mBleDeviceProcessor.stopScanIfNeeded();
}
}
@@ -513,7 +515,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
}
// Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
+ mBleDeviceProcessor.startScan();
onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
// If current device is BLE present but BT is disconnected , means it will be
@@ -724,7 +726,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
final ParcelUuid parcelUuid = uuid.getUuid();
final int userId = uuid.getUserId();
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- onDeviceLocked(/* associationId */ -1, userId, eventType, parcelUuid);
+ onDeviceLocked(NO_ASSOCIATION, userId, eventType, parcelUuid);
return;
}
@@ -930,10 +932,6 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@Override
public void onAssociationRemoved(@NonNull AssociationInfo association) {
final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
mConnectedBtDevices.remove(id);
mNearbyBleDevices.remove(id);
@@ -1004,8 +1002,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (deviceEvents != null) {
deviceEvents.removeIf(deviceEvent ->
deviceEvent.getEvent() == EVENT_BLE_APPEARED
- && Objects.equals(deviceEvent.getUuid(), uuid)
- && deviceEvent.getAssociationId() == associationId);
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
}
}
}
@@ -1018,8 +1016,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (deviceEvents != null) {
deviceEvents.removeIf(deviceEvent ->
deviceEvent.getEvent() == EVENT_BT_CONNECTED
- && Objects.equals(deviceEvent.getUuid(), uuid)
- && deviceEvent.getAssociationId() == associationId);
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
}
}
}
@@ -1054,7 +1052,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
return;
}
- switch(event) {
+ switch (event) {
case EVENT_BLE_APPEARED:
onBleCompanionDeviceFound(
associationInfo.getId(), associationInfo.getUserId());
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
index 9cfa2705cb2f..c9f60ca639d4 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
index fa0f6bd92acb..4678a165b83f 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766eed209..f397814f7ad7 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.companion.utils;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
@@ -209,7 +211,9 @@ public final class PermissionsUtils {
*/
public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
- != PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_SCAN) != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_CONNECT) != PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to request observing device presence base on the UUID");
}
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
index 4a2030f9caff..66313e6ca957 100644
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -10,6 +10,7 @@ java_aconfig_library {
aconfig_declarations {
name: "virtualdevice_flags",
package: "com.android.server.companion.virtual",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8b98d12b0660..215f6402fa76 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -28,8 +28,6 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
import android.annotation.EnforcePermission;
@@ -1068,6 +1066,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
public boolean hasCustomAudioInputSupport() throws RemoteException {
+ return hasCustomAudioInputSupportInternal();
+ }
+
+ private boolean hasCustomAudioInputSupportInternal() {
if (!Flags.vdmPublicApis()) {
return false;
}
@@ -1079,8 +1081,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return false;
}
- if (getDevicePolicy(POLICY_TYPE_AUDIO) == VirtualDeviceParams.DEVICE_POLICY_DEFAULT) {
- return false;
+ if (getDevicePolicy(POLICY_TYPE_AUDIO) == VirtualDeviceParams.DEVICE_POLICY_CUSTOM) {
+ return true;
}
final long token = Binder.clearCallingIdentity();
try {
@@ -1108,10 +1110,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mParams.dump(fout, indent + indent);
fout.println(indent + "mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
- fout.println(" mDevicePolicies: " + mDevicePolicies);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
fout.println(indent + " " + mVirtualDisplays.keyAt(i));
}
+ fout.println(" mDevicePolicies: " + mDevicePolicies);
fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
@@ -1119,6 +1121,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
if (mVirtualCameraController != null) {
mVirtualCameraController.dump(fout, indent);
}
+ fout.println(
+ indent + "hasCustomAudioInputSupport: " + hasCustomAudioInputSupportInternal());
}
// For display mirroring, we want to dispatch all key events to the source (default) display,
@@ -1150,8 +1154,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
- FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
mAttributionSource,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
@@ -1265,7 +1269,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
// if the secure window is shown on a non-secure virtual display.
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
Display display = displayManager.getDisplay(displayId);
- if ((display.getFlags() & FLAG_SECURE) == 0) {
+ if ((display.getFlags() & Display.FLAG_SECURE) == 0) {
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
index 6297e91e8705..616f5d09e13f 100644
--- a/services/companion/java/com/android/server/companion/virtual/flags.aconfig
+++ b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
@@ -1,6 +1,7 @@
# OLD PACKAGE, DO NOT USE: Prefer `flags.aconfig` in core/java/android/companion/virtual
# (or other custom files) to define your flags
package: "com.android.server.companion.virtual"
+container: "system"
flag {
name: "dump_history"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7f5867fb1a74..fd7e4abc6d16 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -246,7 +246,7 @@ java_library_static {
"biometrics_flags_lib",
"am_flags_lib",
"com_android_server_accessibility_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
@@ -254,6 +254,7 @@ java_library_static {
"net_flags_lib",
"stats_flags_lib",
"core_os_flags_lib",
+ "connectivity_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index ac19d8bc897f..4694e9fd44bc 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -70,6 +70,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
NotificationListener mNotificationListener;
@Nullable
private MediaProjectionManager mProjectionManager;
+
+ @GuardedBy("mSensitiveContentProtectionLock")
@Nullable
private MediaProjectionSession mMediaProjectionSession;
@@ -98,6 +100,48 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
mIsExempted = isExempted;
mSessionId = sessionId;
}
+
+ public void logProjectionSessionStart() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logProjectionSessionStop() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logAppBlocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
+ );
+ }
+
+ public void logAppUnblocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
+ );
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -112,28 +156,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
-
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStop");
try {
@@ -242,16 +269,18 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
projectionInfo.getUserHandle().getIdentifier());
- mMediaProjectionSession = new MediaProjectionSession(
- uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
-
- if (isPackageExempted || isFeatureDisabled) {
- Log.w(TAG, "projection session is exempted, package ="
- + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
- return;
- }
-
synchronized (mSensitiveContentProtectionLock) {
+ mMediaProjectionSession = new MediaProjectionSession(
+ uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+ mMediaProjectionSession.logProjectionSessionStart();
+
+ if (isPackageExempted || isFeatureDisabled) {
+ Log.w(TAG, "projection session is exempted, package ="
+ + projectionInfo.getPackageName() + ", isFeatureDisabled="
+ + isFeatureDisabled);
+ return;
+ }
+
mProjectionActive = true;
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
@@ -266,7 +295,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private void onProjectionEnd() {
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = false;
- mMediaProjectionSession = null;
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logProjectionSessionStop();
+ mMediaProjectionSession = null;
+ }
// notify windowmanager to clear any sensitive notifications observed during projection
// session
@@ -437,22 +469,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
packageInfos.add(packageInfo);
if (isShowingSensitiveContent) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppBlocked(uid);
+ }
} else {
mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppUnblocked(uid);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 25337a434329..baae40b88e89 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -62,6 +62,18 @@
"file_patterns": ["SensorPrivacyService\\.java"]
},
{
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.SensitiveContentProtectionManagerServiceContentTest"
+ },
+ {
+ "include-filter": "com.android.server.SensitiveContentProtectionManagerServiceNotificationTest"
+ }
+ ],
+ "file_patterns": ["SensitiveContentProtectionManagerService\\.java"]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
@@ -163,6 +175,9 @@
}
],
"file_patterns": ["PinnerService\\.java"]
+ },
+ {
+ "name": "SelinuxFrameworksTests"
}
]
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 586b09517e92..603a95c31e5b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -897,6 +897,11 @@ public class AccountManagerService
}
}
for (String packageNameToNotify : accountRemovedReceivers) {
+ int currentVisibility =
+ resolveAccountVisibility(account, packageNameToNotify, accounts);
+ if (isVisible(currentVisibility)) {
+ continue;
+ }
sendAccountRemovedBroadcast(
account,
packageNameToNotify,
diff --git a/services/core/java/com/android/server/am/AccessCheckDelegateHelper.java b/services/core/java/com/android/server/am/AccessCheckDelegateHelper.java
new file mode 100644
index 000000000000..62c6329ced37
--- /dev/null
+++ b/services/core/java/com/android/server/am/AccessCheckDelegateHelper.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.am;
+
+import android.annotation.Nullable;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.appop.AppOpsService;
+import com.android.server.pm.permission.AccessCheckDelegate;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.util.Collections;
+import java.util.List;
+
+class AccessCheckDelegateHelper {
+ private final ActivityManagerGlobalLock mProcLock;
+
+ @GuardedBy("mProcLock")
+ private final List<ActiveInstrumentation> mActiveInstrumentation;
+
+ private final AppOpsService mAppOpsService;
+
+ private final PermissionManagerServiceInternal mPermissionManagerInternal;
+
+ @GuardedBy("mProcLock")
+ private AccessCheckDelegate mAccessCheckDelegate;
+
+ AccessCheckDelegateHelper(ActivityManagerGlobalLock procLock,
+ List<ActiveInstrumentation> activeInstrumentation, AppOpsService appOpsService,
+ PermissionManagerServiceInternal permissionManagerInternal) {
+ mProcLock = procLock;
+ mActiveInstrumentation = activeInstrumentation;
+ mAppOpsService = appOpsService;
+ mPermissionManagerInternal = permissionManagerInternal;
+ }
+
+ @GuardedBy("mProcLock")
+ private AccessCheckDelegate getAccessCheckDelegateLPr(boolean create) {
+ if (create && mAccessCheckDelegate == null) {
+ mAccessCheckDelegate = new AccessCheckDelegate.AccessCheckDelegateImpl();
+ mAppOpsService.setCheckOpsDelegate(mAccessCheckDelegate);
+ mPermissionManagerInternal.setCheckPermissionDelegate(mAccessCheckDelegate);
+ }
+
+ return mAccessCheckDelegate;
+ }
+
+ @GuardedBy("mProcLock")
+ private void removeAccessCheckDelegateLPr() {
+ mAccessCheckDelegate = null;
+ mAppOpsService.setCheckOpsDelegate(null);
+ mPermissionManagerInternal.setCheckPermissionDelegate(null);
+ }
+
+ void startDelegateShellPermissionIdentity(int delegateUid,
+ @Nullable String[] permissions) {
+ if (UserHandle.getCallingAppId() != Process.SHELL_UID
+ && UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only the shell can delegate its permissions");
+ }
+
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate != null && !delegate.isDelegateAndOwnerUid(delegateUid)) {
+ throw new SecurityException("Shell can delegate permissions only "
+ + "to one instrumentation at a time");
+ }
+ final int instrCount = mActiveInstrumentation.size();
+ for (int i = 0; i < instrCount; i++) {
+ final ActiveInstrumentation instr =
+ mActiveInstrumentation.get(i);
+ if (instr.mTargetInfo.uid != delegateUid) {
+ continue;
+ }
+
+ // If instrumentation started from the shell the connection is not null
+ if (instr.mUiAutomationConnection == null) {
+ throw new SecurityException("Shell can delegate its permissions"
+ + " only to an instrumentation started from the shell");
+ }
+
+ final String packageName = instr.mTargetInfo.packageName;
+ delegate = getAccessCheckDelegateLPr(true);
+ delegate.setShellPermissionDelegate(delegateUid, packageName, permissions);
+ return;
+ }
+ }
+ }
+
+ void stopDelegateShellPermissionIdentity() {
+ if (UserHandle.getCallingAppId() != Process.SHELL_UID
+ && UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only the shell can delegate its permissions");
+ }
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate == null) {
+ return;
+ }
+
+ if (!delegate.hasShellPermissionDelegate()) {
+ return;
+ }
+
+ delegate.removeShellPermissionDelegate();
+
+ if (!delegate.hasDelegateOrOverrides()) {
+ removeAccessCheckDelegateLPr();
+ }
+ }
+ }
+
+ List<String> getDelegatedShellPermissions() {
+ if (UserHandle.getCallingAppId() != Process.SHELL_UID
+ && UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only the shell can get delegated permissions");
+ }
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate == null) {
+ return Collections.EMPTY_LIST;
+ }
+
+ return delegate.getDelegatedPermissionNames();
+ }
+ }
+
+ void addOverridePermissionState(int originatingUid, int uid, String permission, int result) {
+ if (UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can override permissions");
+ }
+
+ synchronized (mProcLock) {
+ final int instrCount = mActiveInstrumentation.size();
+ for (int i = 0; i < instrCount; i++) {
+ final ActiveInstrumentation instr =
+ mActiveInstrumentation.get(i);
+ if (instr.mTargetInfo.uid != originatingUid) {
+ continue;
+ }
+ // If instrumentation started from the shell the connection is not null
+ if (instr.mSourceUid != Process.ROOT_UID || instr.mUiAutomationConnection == null) {
+ throw new SecurityException("Root can only override permissions only if the "
+ + "owning app was instrumented from root.");
+ }
+
+ AccessCheckDelegate delegate =
+ getAccessCheckDelegateLPr(true);
+ if (delegate.hasOverriddenPermissions()
+ && !delegate.isDelegateAndOwnerUid(originatingUid)) {
+ throw new SecurityException("Only one instrumentation to grant"
+ + " overrides is allowed at a time.");
+ }
+
+ delegate.addOverridePermissionState(originatingUid, uid, permission, result);
+ return;
+ }
+ }
+ }
+
+ void removeOverridePermissionState(int originatingUid, int uid, String permission) {
+ if (UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can override permissions.");
+ }
+
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate == null) {
+ return;
+ }
+
+ if (!delegate.isDelegateAndOwnerUid(originatingUid)) {
+ if (delegate.hasOverriddenPermissions()) {
+ throw new SecurityException("Only the granter of current overrides can remove "
+ + "them.");
+ }
+ return;
+ }
+
+ delegate.removeOverridePermissionState(uid, permission);
+
+ if (!delegate.hasDelegateOrOverrides()) {
+ removeAccessCheckDelegateLPr();
+ }
+ }
+ }
+
+ void clearOverridePermissionStates(int originatingUid, int uid) {
+ if (UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can override permissions.");
+ }
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate == null) {
+ return;
+ }
+
+ if (!delegate.isDelegateAndOwnerUid(originatingUid)) {
+ if (delegate.hasOverriddenPermissions()) {
+ throw new SecurityException(
+ "Only the granter of current overrides can remove them.");
+ }
+ return;
+ }
+
+ delegate.clearOverridePermissionStates(uid);
+
+ if (!delegate.hasDelegateOrOverrides()) {
+ removeAccessCheckDelegateLPr();
+ }
+ }
+ }
+
+ void clearAllOverridePermissionStates(int originatingUid) {
+ if (UserHandle.getCallingAppId() != Process.ROOT_UID) {
+ throw new SecurityException("Only root can override permissions.");
+ }
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate == null) {
+ return;
+ }
+
+ if (!delegate.isDelegateAndOwnerUid(originatingUid)) {
+ if (delegate.hasOverriddenPermissions()) {
+ throw new SecurityException(
+ "Only the granter of current overrides can remove them.");
+ }
+ return;
+ }
+
+ delegate.clearAllOverridePermissionStates();
+
+ if (!delegate.hasDelegateOrOverrides()) {
+ removeAccessCheckDelegateLPr();
+ }
+ }
+ }
+
+ void onInstrumentationFinished(int uid, String packageName) {
+ synchronized (mProcLock) {
+ AccessCheckDelegate delegate = getAccessCheckDelegateLPr(false);
+ if (delegate != null) {
+ if (delegate.isDelegatePackage(uid, packageName)) {
+ delegate.removeShellPermissionDelegate();
+ }
+ if (delegate.isDelegateAndOwnerUid(uid)) {
+ delegate.clearAllOverridePermissionStates();
+ }
+ if (!delegate.hasDelegateOrOverrides()) {
+ removeAccessCheckDelegateLPr();
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0a2aaeba57b4..7ea82b095533 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5555,6 +5555,7 @@ public final class ActiveServices {
boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
throws TransactionTooLargeException {
if (r.app != null && r.app.isThreadReady()) {
+ r.updateOomAdjSeq();
sendServiceArgsLocked(r, execInFg, false);
return null;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 55b161ad6348..dcda5c228ceb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -55,9 +55,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_BACKGROUND_CHECK = DEBUG_ALL || false;
static final boolean DEBUG_BACKUP = DEBUG_ALL || false;
static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
- static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
- static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
static final boolean DEBUG_FREEZER = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0bc2a91e1935..ad15ea90c45c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -51,7 +51,6 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
-import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BACKUP;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION;
@@ -251,7 +250,6 @@ import android.app.PendingIntentStats;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
import android.app.ServiceStartNotAllowedException;
-import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.assist.ActivityId;
import android.app.backup.BackupAnnotations.BackupDestination;
@@ -427,17 +425,11 @@ import com.android.internal.os.Zygote;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.DodecFunction;
-import com.android.internal.util.function.HexFunction;
-import com.android.internal.util.function.OctFunction;
-import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.UndecFunction;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -643,7 +635,8 @@ public class ActivityManagerService extends IActivityManager.Stub
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
-
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
/**
* It is now required for apps to explicitly set either
* {@link android.content.Context#RECEIVER_EXPORTED} or
@@ -732,6 +725,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
+ /** Whether some specified important processes are allowed to use FIFO priority. */
+ boolean mAllowSpecifiedFifoScheduling = true;
+
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
@@ -770,6 +766,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("mDeliveryGroupPolicyIgnoredActions")
private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
+ private AccessCheckDelegateHelper mAccessCheckDelegateHelper;
+
/**
* Uids of apps with current active camera sessions. Access synchronized on
* the IntArray instance itself, and no other locks must be acquired while that
@@ -1053,6 +1051,10 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
+ /** The processes that are allowed to use SCHED_FIFO prorioty. */
+ @GuardedBy("mProcLock")
+ final ArrayList<ProcessRecord> mSpecifiedFifoProcesses = new ArrayList<>();
+
/**
* List of records for processes that someone had tried to start before the
* system was ready. We don't start them at that point, but ensure they
@@ -4424,7 +4426,9 @@ public class ActivityManagerService extends IActivityManager.Stub
packageName, null, userId);
}
- if (packageName == null || uninstalling || packageStateStopped) {
+ final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
+ && packageStateStopped);
+ if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
packageName, userId, appId, doit);
}
@@ -6937,6 +6941,16 @@ public class ActivityManagerService extends IActivityManager.Stub
return mPermissionManagerInt;
}
+ private AccessCheckDelegateHelper getAccessCheckDelegateHelper() {
+ // Intentionally hold no locks: in case of race conditions, the mPermissionManagerInt will
+ // be set to the same value anyway.
+ if (mAccessCheckDelegateHelper == null) {
+ mAccessCheckDelegateHelper = new AccessCheckDelegateHelper(mProcLock,
+ mActiveInstrumentation, mAppOpsService, getPermissionManagerInternal());
+ }
+ return mAccessCheckDelegateHelper;
+ }
+
/** Returns whether the given package was ever launched since install */
boolean wasPackageEverLaunched(String packageName, @UserIdInt int userId) {
boolean wasLaunched = false;
@@ -7650,6 +7664,16 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
public void requestBugReportWithDescription(@Nullable String shareTitle,
@Nullable String shareDescription, int bugreportType, long nonce) {
+ requestBugReportWithDescription(shareTitle, shareDescription, bugreportType, nonce, null);
+ }
+
+ /**
+ * Takes a bugreport using bug report API ({@code BugreportManager}) which gets
+ * triggered by sending a broadcast to Shell. Optionally adds an extra attachment.
+ */
+ public void requestBugReportWithDescription(@Nullable String shareTitle,
+ @Nullable String shareDescription, int bugreportType, long nonce,
+ @Nullable Uri extraAttachment) {
String type = null;
switch (bugreportType) {
case BugreportParams.BUGREPORT_MODE_FULL:
@@ -7704,6 +7728,10 @@ public class ActivityManagerService extends IActivityManager.Stub
triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_NONCE, nonce);
+ if (extraAttachment != null) {
+ triggerShellBugreport.putExtra(EXTRA_EXTRA_ATTACHMENT_URI, extraAttachment);
+ triggerShellBugreport.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (shareTitle != null) {
@@ -7757,6 +7785,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Takes an interactive bugreport with a progress notification. Also attaches given file uri.
+ */
+ @Override
+ public void requestBugReportWithExtraAttachment(@NonNull Uri extraAttachment) {
+ requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE, 0L,
+ extraAttachment);
+ }
+
+ /**
* Takes an interactive bugreport with a progress notification. Also, shows the given title and
* description on the final share notification
*/
@@ -8214,6 +8251,27 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
+ /**
+ * Switches the priority between SCHED_FIFO and SCHED_OTHER for the main thread and render
+ * thread of the given process.
+ */
+ @GuardedBy("mProcLock")
+ static void setFifoPriority(@NonNull ProcessRecord app, boolean enable) {
+ final int pid = app.getPid();
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (enable) {
+ scheduleAsFifoPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsFifoPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ } else {
+ scheduleAsRegularPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsRegularPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ }
+ }
+
@Override
public void setRenderThread(int tid) {
synchronized (mProcLock) {
@@ -8239,7 +8297,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// promote to FIFO now
if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
- if (mUseFifoUiScheduling) {
+ if (proc.useFifoUiScheduling()) {
setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
@@ -11276,6 +11334,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mAlwaysFinishActivities) {
pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities);
}
+ if (mAllowSpecifiedFifoScheduling) {
+ pw.println(" mAllowSpecifiedFifoScheduling=true");
+ }
if (dumpAll) {
pw.println(" Total persistent processes: " + numPers);
pw.println(" mProcessesReady=" + mProcessesReady
@@ -12080,18 +12141,23 @@ public class ActivityManagerService extends IActivityManager.Stub
for (int i=0; i<items.size(); i++) {
MemItem mi = items.get(i);
if (!isCompact) {
- pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+ String printFormat = "%s%s: %s%s\n";
+ if ((dumpPss && dumpSwapPss) || dumpPrivateDirty) {
+ StringBuilder format = new StringBuilder();
+ format.append("%s%s: %-60s%s");
+ if (dumpSwapPss) {
+ format.append(String.format("(%s in swap%s", stringifyKBSize(mi.swapPss),
+ dumpPrivateDirty ? ", " : ")"));
+ }
+ if (dumpPrivateDirty) {
+ format.append(String.format("%s%s private dirty)", dumpSwapPss ? "" : "(",
+ stringifyKBSize(mi.mPrivateDirty)));
+ }
+ printFormat = format.append("\n").toString();
+ }
+ pw.printf(printFormat, prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
mi.label,
mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : "");
- if (dumpPss && dumpSwapPss) {
- pw.printf("(%s in swap%s", stringifyKBSize(mi.swapPss),
- dumpPrivateDirty ? ", " : ")");
- }
- if (dumpPrivateDirty) {
- pw.printf("%s%s private dirty)", dumpSwapPss ? "" : "(",
- stringifyKBSize(mi.mPrivateDirty));
- }
- pw.printf("\n");
} else if (mi.isProc) {
pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
pw.print(","); pw.print(mi.id); pw.print(",");
@@ -16598,8 +16664,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op.
mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid,
app.info.packageName, AppOpsManager.MODE_ERRORED);
- mAppOpsService.setAppOpsServiceDelegate(null);
- getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
+ getAccessCheckDelegateHelper()
+ .onInstrumentationFinished(app.uid, app.info.packageName);
mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
instr.mUiAutomationConnection).sendToTarget();
}
@@ -17339,6 +17405,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
}
+
+ if (com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()) {
+ synchronized (mProcLock) {
+ adjustFifoProcessesIfNeeded(uid, !active /* allowFifo */);
+ }
+ }
}
final boolean isCameraActiveForUid(@UserIdInt int uid) {
@@ -17347,6 +17419,34 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * This is called when the given uid is using camera. If the uid has top process state, then
+ * cancel the FIFO priority of the high priority processes.
+ */
+ @VisibleForTesting
+ @GuardedBy("mProcLock")
+ void adjustFifoProcessesIfNeeded(int preemptiveUid, boolean allowSpecifiedFifo) {
+ if (allowSpecifiedFifo == mAllowSpecifiedFifoScheduling) {
+ return;
+ }
+ if (!allowSpecifiedFifo) {
+ final UidRecord uidRec = mProcessList.mActiveUids.get(preemptiveUid);
+ if (uidRec == null || uidRec.getCurProcState() > PROCESS_STATE_TOP) {
+ // To avoid frequent switching by background camera usages, e.g. face unlock,
+ // face detection (auto rotation), screen attention (keep screen on).
+ return;
+ }
+ }
+ mAllowSpecifiedFifoScheduling = allowSpecifiedFifo;
+ for (int i = mSpecifiedFifoProcesses.size() - 1; i >= 0; i--) {
+ final ProcessRecord proc = mSpecifiedFifoProcesses.get(i);
+ if (proc.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_TOP_APP) {
+ continue;
+ }
+ setFifoPriority(proc, allowSpecifiedFifo /* enable */);
+ }
+ }
+
@GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
@@ -17613,7 +17713,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
- boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ boolean runGc, String dumpBitmaps,
+ String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
try {
// note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
// its own permission (same as profileControl).
@@ -17647,7 +17748,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}, null);
- thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
+ thread.dumpHeap(managed, mallocInfo, runGc, dumpBitmaps,
+ path, fd, intermediateCallback);
fd = null;
return true;
}
@@ -17859,9 +17961,35 @@ public class ActivityManagerService extends IActivityManager.Stub
mUserController.setStopUserOnSwitch(value);
}
+ /** @deprecated use {@link #stopUserWithCallback(int, IStopUserCallback)} instead */
+ @Deprecated
+ @Override
+ public int stopUser(final int userId,
+ boolean stopProfileRegardlessOfParent, final IStopUserCallback callback) {
+ return stopUserExceptCertainProfiles(userId, stopProfileRegardlessOfParent, callback);
+ }
+
+ /** Stops the given user. */
+ @Override
+ public int stopUserWithCallback(@UserIdInt int userId, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops the given user.
+ *
+ * Usually, callers can just use @link{#stopUserWithCallback(int, IStopUserCallback)} instead.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user;
+ * its value is irrelevant for non-profile users.
+ */
@Override
- public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ public int stopUserExceptCertainProfiles(@UserIdInt int userId,
+ boolean stopProfileRegardlessOfParent, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId,
+ stopProfileRegardlessOfParent, /* allowDelayedLocking= */ false,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
@@ -17870,11 +17998,9 @@ public class ActivityManagerService extends IActivityManager.Stub
* stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
*
* <p>When delayed locking is not enabled through the overlay, this call becomes the same
- * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ * with {@link #stopUserWithCallback(int, IStopUserCallback)} call.
*
* @param userId User id to stop.
- * @param force Force stop the user even if the user is related with system user or current
- * user.
* @param callback Callback called when user has stopped.
*
* @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
@@ -17884,9 +18010,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
// delayed locking behavior once the private space flag is finalized.
@Override
- public int stopUserWithDelayedLocking(final int userId, boolean force,
- final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ public int stopUserWithDelayedLocking(@UserIdInt int userId, IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ true,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
@@ -18122,7 +18247,8 @@ public class ActivityManagerService extends IActivityManager.Stub
public ComponentName startSdkSandboxService(Intent service, int clientAppUid,
String clientAppPackage, String processName) throws RemoteException {
validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
- if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
+ if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage)
+ != AppOpsManager.MODE_ALLOWED) {
throw new IllegalArgumentException("uid does not belong to provided package");
}
// TODO(b/269598719): Is passing the application thread of the system_server alright?
@@ -18191,7 +18317,8 @@ public class ActivityManagerService extends IActivityManager.Stub
String processName, long flags)
throws RemoteException {
validateSdkSandboxParams(service, clientAppUid, clientAppPackage, processName);
- if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage) != MODE_ALLOWED) {
+ if (mAppOpsService.checkPackage(clientAppUid, clientAppPackage)
+ != AppOpsManager.MODE_ALLOWED) {
throw new IllegalArgumentException("uid does not belong to provided package");
}
if (conn == null) {
@@ -20499,268 +20626,41 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void startDelegateShellPermissionIdentity(int delegateUid,
@Nullable String[] permissions) {
- if (UserHandle.getCallingAppId() != Process.SHELL_UID
- && UserHandle.getCallingAppId() != Process.ROOT_UID) {
- throw new SecurityException("Only the shell can delegate its permissions");
- }
-
- // We allow delegation only to one instrumentation started from the shell
- synchronized (mProcLock) {
- // If the delegate is already set up for the target UID, nothing to do.
- if (mAppOpsService.getAppOpsServiceDelegate() != null) {
- if (!(mAppOpsService.getAppOpsServiceDelegate() instanceof ShellDelegate)) {
- throw new IllegalStateException("Bad shell delegate state");
- }
- final ShellDelegate delegate = (ShellDelegate) mAppOpsService
- .getAppOpsServiceDelegate();
- if (delegate.getDelegateUid() != delegateUid) {
- throw new SecurityException("Shell can delegate permissions only "
- + "to one instrumentation at a time");
- }
- }
-
- final int instrCount = mActiveInstrumentation.size();
- for (int i = 0; i < instrCount; i++) {
- final ActiveInstrumentation instr = mActiveInstrumentation.get(i);
- if (instr.mTargetInfo.uid != delegateUid) {
- continue;
- }
- // If instrumentation started from the shell the connection is not null
- if (instr.mUiAutomationConnection == null) {
- throw new SecurityException("Shell can delegate its permissions" +
- " only to an instrumentation started from the shell");
- }
-
- // Hook them up...
- final ShellDelegate shellDelegate = new ShellDelegate(delegateUid,
- permissions);
- mAppOpsService.setAppOpsServiceDelegate(shellDelegate);
- final String packageName = instr.mTargetInfo.packageName;
- final List<String> permissionNames = permissions != null ?
- Arrays.asList(permissions) : null;
- getPermissionManagerInternal().startShellPermissionIdentityDelegation(
- delegateUid, packageName, permissionNames);
- return;
- }
- }
+ getAccessCheckDelegateHelper()
+ .startDelegateShellPermissionIdentity(delegateUid, permissions);
}
@Override
public void stopDelegateShellPermissionIdentity() {
- if (UserHandle.getCallingAppId() != Process.SHELL_UID
- && UserHandle.getCallingAppId() != Process.ROOT_UID) {
- throw new SecurityException("Only the shell can delegate its permissions");
- }
- synchronized (mProcLock) {
- mAppOpsService.setAppOpsServiceDelegate(null);
- getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
- }
+ getAccessCheckDelegateHelper().stopDelegateShellPermissionIdentity();
}
@Override
public List<String> getDelegatedShellPermissions() {
- if (UserHandle.getCallingAppId() != Process.SHELL_UID
- && UserHandle.getCallingAppId() != Process.ROOT_UID) {
- throw new SecurityException("Only the shell can get delegated permissions");
- }
- synchronized (mProcLock) {
- return getPermissionManagerInternal().getDelegatedShellPermissions();
- }
+ return getAccessCheckDelegateHelper().getDelegatedShellPermissions();
}
- private class ShellDelegate implements CheckOpsDelegate {
- private final int mTargetUid;
- @Nullable
- private final String[] mPermissions;
-
- ShellDelegate(int targetUid, @Nullable String[] permissions) {
- mTargetUid = targetUid;
- mPermissions = permissions;
- }
-
- int getDelegateUid() {
- return mTargetUid;
- }
-
- @Override
- public int checkOperation(int code, int uid, String packageName, String attributionTag,
- int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String,
- Integer, Boolean, Integer> superImpl) {
- if (uid == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
- Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(code, shellUid, "com.android.shell", null,
- virtualDeviceId, raw);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(code, uid, packageName, attributionTag, virtualDeviceId, raw);
- }
-
- @Override
- public int checkAudioOperation(int code, int usage, int uid, String packageName,
- QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) {
- if (uid == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
- Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(code, usage, shellUid, "com.android.shell");
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(code, usage, uid, packageName);
- }
-
- @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) {
- if (uid == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
- Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(code, shellUid, "com.android.shell", featureId,
- virtualDeviceId, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
- }
-
- @Override
- public SyncNotedAppOp noteProxyOperation(int code,
- @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation,
- @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
- Boolean, SyncNotedAppOp> superImpl) {
- if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(
- attributionSource.getUid()), Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(code, new AttributionSource(shellUid,
- Process.INVALID_PID, "com.android.shell",
- attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
- attributionSource.getNext()),
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skiProxyOperation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, skiProxyOperation);
- }
-
- @Override
- public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
- @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
- @Nullable String message, boolean shouldCollectMessage,
- @AttributionFlags int attributionFlags, int attributionChainId,
- @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean,
- Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
- if (uid == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
- Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(token, code, shellUid, "com.android.shell",
- attributionTag, virtualDeviceId, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- attributionFlags, attributionChainId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(token, code, uid, packageName, attributionTag, virtualDeviceId,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- attributionFlags, attributionChainId);
- }
-
- @Override
- public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
- @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
- boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
- boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
- @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
- @NonNull UndecFunction<IBinder, Integer, AttributionSource,
- Boolean, Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
- SyncNotedAppOp> superImpl) {
- if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(
- attributionSource.getUid()), Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(clientId, code, new AttributionSource(shellUid,
- Process.INVALID_PID, "com.android.shell",
- attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
- attributionSource.getNext()),
- startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
- proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
- }
+ @Override
+ public void addOverridePermissionState(int originatingUid, int uid, String permission,
+ int result) {
+ getAccessCheckDelegateHelper()
+ .addOverridePermissionState(originatingUid, uid, permission, result);
+ }
- @Override
- public void finishProxyOperation(@NonNull IBinder clientId, int code,
- @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
- @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
- Void> superImpl) {
- if (attributionSource.getUid() == mTargetUid && isTargetOp(code)) {
- final int shellUid = UserHandle.getUid(UserHandle.getUserId(
- attributionSource.getUid()), Process.SHELL_UID);
- final long identity = Binder.clearCallingIdentity();
- try {
- superImpl.apply(clientId, code, new AttributionSource(shellUid,
- Process.INVALID_PID, "com.android.shell",
- attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
- attributionSource.getNext()),
- skipProxyOperation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
- }
+ @Override
+ public void removeOverridePermissionState(int originatingUid, int uid, String permission) {
+ getAccessCheckDelegateHelper()
+ .removeOverridePermissionState(originatingUid, uid, permission);
+ }
- private boolean isTargetOp(int code) {
- // null permissions means all ops are targeted
- if (mPermissions == null) {
- return true;
- }
- // no permission for the op means the op is targeted
- final String permission = AppOpsManager.opToPermission(code);
- if (permission == null) {
- return true;
- }
- return isTargetPermission(permission);
- }
+ @Override
+ public void clearOverridePermissionStates(int originatingUid, int uid) {
+ getAccessCheckDelegateHelper().clearOverridePermissionStates(originatingUid, uid);
+ }
- private boolean isTargetPermission(@NonNull String permission) {
- // null permissions means all permissions are targeted
- return (mPermissions == null || ArrayUtils.contains(mPermissions, permission));
- }
+ @Override
+ public void clearAllOverridePermissionStates(int originatingUid) {
+ getAccessCheckDelegateHelper().clearAllOverridePermissionStates(originatingUid);
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5a97e87f53f7..e70722ca6579 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1239,6 +1239,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
final PrintWriter err = getErrPrintWriter();
boolean managed = true;
boolean mallocInfo = false;
+ String dumpBitmaps = null;
int userId = UserHandle.USER_CURRENT;
boolean runGc = false;
@@ -1257,6 +1258,11 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else if (opt.equals("-m")) {
managed = false;
mallocInfo = true;
+ } else if (opt.equals("-b")) {
+ dumpBitmaps = getNextArg();
+ if (dumpBitmaps == null) {
+ dumpBitmaps = "png"; // default to PNG in dumping bitmaps
+ }
} else {
err.println("Error: Unknown option: " + opt);
return -1;
@@ -1288,8 +1294,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}, null);
- if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd,
- finishCallback)) {
+ if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, dumpBitmaps,
+ heapFile, fd, finishCallback)) {
err.println("HEAP DUMP FAILED on process " + process);
return -1;
}
@@ -2554,7 +2560,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"shell_runStopUser-" + userId + "-[stopUser]");
try {
- int res = mInterface.stopUser(userId, force, callback);
+ int res = mInterface.stopUserExceptCertainProfiles(
+ userId, /* stopProfileRegardlessOfParent= */ force, callback);
if (res != ActivityManager.USER_OP_SUCCESS) {
String txt = "";
switch (res) {
@@ -4284,11 +4291,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" --user <USER_ID> | current: When supplying a process name,");
pw.println(" specify user of process to profile; uses current user if not");
pw.println(" specified.");
- pw.println(" dumpheap [--user <USER_ID> current] [-n] [-g] <PROCESS> <FILE>");
+ pw.println(" dumpheap [--user <USER_ID> current] [-n] [-g] [-b <format>] ");
+ pw.println(" <PROCESS> <FILE>");
pw.println(" Dump the heap of a process. The given <PROCESS> argument may");
pw.println(" be either a process name or pid. Options are:");
pw.println(" -n: dump native heap instead of managed heap");
pw.println(" -g: force GC before dumping the heap");
+ pw.println(" -b <format>: dump contents of bitmaps in the format specified,");
+ pw.println(" which can be \"png\", \"jpg\" or \"webp\".");
pw.println(" --user <USER_ID> | current: When supplying a process name,");
pw.println(" specify user of process to dump; uses current user if not specified.");
pw.println(" set-debug-app [-w] [--persistent] <PACKAGE>");
@@ -4385,7 +4395,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Stop execution of USER_ID, not allowing it to run any");
pw.println(" code until a later explicit start or switch to it.");
pw.println(" -w: wait for stop-user to complete.");
- pw.println(" -f: force stop even if there are related users that cannot be stopped.");
+ pw.println(" -f: force stop, even if user has an unstoppable parent.");
pw.println(" is-user-stopped <USER_ID>");
pw.println(" Returns whether <USER_ID> has been stopped or not.");
pw.println(" get-started-user-state <USER_ID>");
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
index af1200e4bdf8..0294ffe6e151 100644
--- a/services/core/java/com/android/server/am/Android.bp
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "am_flags",
package: "com.android.server.am",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 147f8d1a1e32..374abe0256c1 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -2014,7 +2014,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
if (!mBgCurrentDrainHighThresholdByBgLocation) {
return false;
}
- if (mTracker.mContext.checkPermission(ACCESS_BACKGROUND_LOCATION,
+ if (mTracker.mInjector.checkPermission(ACCESS_BACKGROUND_LOCATION,
Process.INVALID_PID, uid) == PERMISSION_GRANTED) {
return true;
}
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index c641b35475e9..a47beae1d9cb 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -293,7 +293,7 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
mPermissionGranted = true;
return;
}
- mPermissionGranted = mContext.checkPermission(mPermission, Process.INVALID_PID, mUid)
+ mPermissionGranted = mInjector.checkPermission(mPermission, Process.INVALID_PID, mUid)
== PERMISSION_GRANTED;
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 51aae771542e..6c16fba048bf 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1051,7 +1051,9 @@ public class AppProfiler {
+ mProfile.mApp + " to " + mDumpUri.getPath());
}
thread.dumpHeap(/* managed= */ true,
- /* mallocInfo= */ false, /* runGc= */ false,
+ /* mallocInfo= */ false,
+ /* runGc= */ false,
+ /* dumpbitmaps= */ null,
mDumpUri.getPath(), fd,
/* finishCallback= */ null);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 88f6bc91d1ff..8b1300b641a9 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -235,6 +235,7 @@ public final class AppRestrictionController {
private final HandlerThread mBgHandlerThread;
private final BgHandler mBgHandler;
private final HandlerExecutor mBgExecutor;
+ private final HandlerExecutor mExecutor;
// No lock is needed, as it's immutable after initialization in constructor.
private final ArrayList<BaseAppStateTracker> mAppStateTrackers = new ArrayList<>();
@@ -1489,6 +1490,7 @@ public final class AppRestrictionController {
mConstantsObserver = new ConstantsObserver(mBgHandler, mContext);
mNotificationHelper = new NotificationHelper(this);
injector.initAppStateTrackers(this);
+ mExecutor = new HandlerExecutor(injector.getDefaultHandler());
}
void onSystemReady() {
@@ -1506,7 +1508,7 @@ public final class AppRestrictionController {
mInjector.getAppStateTracker().addBackgroundRestrictedAppListener(
mBackgroundRestrictionListener);
mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener);
- mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor,
+ mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mExecutor,
mRoleHolderChangedListener, UserHandle.ALL);
mInjector.scheduleInitTrackers(mBgHandler, () -> {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
@@ -2896,7 +2898,7 @@ public final class AppRestrictionController {
for (int i = 0; i < numPhones; i++) {
final PhoneCarrierPrivilegesCallback callback = new PhoneCarrierPrivilegesCallback(i);
callbacks.add(callback);
- telephonyManager.registerCarrierPrivilegesCallback(i, mBgExecutor, callback);
+ telephonyManager.registerCarrierPrivilegesCallback(i, mExecutor, callback);
}
mCarrierPrivilegesCallbacks = callbacks;
}
@@ -3288,6 +3290,10 @@ public final class AppRestrictionController {
return System.currentTimeMillis();
}
+ Handler getDefaultHandler() {
+ return mAppRestrictionController.mActivityManagerService.mHandler;
+ }
+
boolean isTest() {
return false;
}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
index 8d609104656d..5179d58add16 100644
--- a/services/core/java/com/android/server/am/BaseAppStateTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -75,11 +75,11 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
static final int STATE_TYPE_INDEX_PERMISSION = 4;
protected final AppRestrictionController mAppRestrictionController;
- protected final Injector<T> mInjector;
protected final Context mContext;
protected final Handler mBgHandler;
protected final Object mLock;
protected final ArrayList<StateListener> mStateListeners = new ArrayList<>();
+ final Injector<T> mInjector;
interface StateListener {
void onStateChange(int uid, String packageName, boolean start, long now, int stateType);
@@ -292,6 +292,7 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
RoleManager mRoleManager;
NotificationManagerInternal mNotificationManagerInternal;
IAppOpsService mIAppOpsService;
+ Context mContext;
void setPolicy(T policy) {
mAppStatePolicy = policy;
@@ -316,6 +317,7 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
NotificationManagerInternal.class);
mIAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mContext = context;
getPolicy().onSystemReady();
}
@@ -390,5 +392,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
IAppOpsService getIAppOpsService() {
return mIAppOpsService;
}
+
+ int checkPermission(String perm, int pid, int uid) {
+ return mContext.checkPermission(perm, pid, uid);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index bf7cc10637a7..b517631f35c0 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -9,7 +9,6 @@ mwachens@google.com
sudheersai@google.com
suprabh@google.com
varunshah@google.com
-kwekua@google.com
bookatz@google.com
jji@google.com
@@ -18,6 +17,7 @@ ogunwale@google.com
# Permissions & Packages
patb@google.com
+per-file AccessCheckDelegateHelper.java = file:/core/java/android/permission/OWNERS
# Battery Stats
joeo@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5a750c2ba6c8..ea7a21dd19cd 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,7 +72,6 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYB
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
import static android.media.audio.Flags.roForegroundAudioControl;
-import static android.os.Process.SCHED_OTHER;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
@@ -81,7 +80,6 @@ import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Process.setProcessGroup;
import static android.os.Process.setThreadPriority;
-import static android.os.Process.setThreadScheduler;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -3315,22 +3313,10 @@ public class OomAdjuster {
// do nothing if we already switched to RT
if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
// Switch UI pipeline for app to SCHED_FIFO
state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- mService.scheduleAsFifoPriority(app.getPid(), true);
- if (renderThreadTid != 0) {
- mService.scheduleAsFifoPriority(renderThreadTid,
- /* suppressLogs */true);
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Set RenderThread (TID " +
- renderThreadTid + ") to FIFO");
- }
- } else {
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Not setting RenderThread TID");
- }
- }
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
} else {
// Boost priority for top app UI and render threads
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
@@ -3347,22 +3333,10 @@ public class OomAdjuster {
} else if (oldSchedGroup == SCHED_GROUP_TOP_APP
&& curSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
- try {
- // Reset UI pipeline to SCHED_OTHER
- setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
- setThreadPriority(app.getPid(), state.getSavedPriority());
- if (renderThreadTid != 0) {
- setThreadScheduler(renderThreadTid,
- SCHED_OTHER, 0);
- }
- } catch (IllegalArgumentException e) {
- Slog.w(TAG,
- "Failed to set scheduling policy, thread does not exist:\n"
- + e);
- } catch (SecurityException e) {
- Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
- }
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
} else {
// Reset priority for top app UI and render threads
setThreadPriority(app.getPid(), 0);
@@ -3557,7 +3531,7 @@ public class OomAdjuster {
// {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
mService.scheduleAsFifoPriority(app.getPid(), true);
} else {
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index a8fe734f59b7..ea925716b864 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -25,6 +25,8 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import dalvik.annotation.optimization.NeverCompile;
+
/**
* The state info of app when it's cached, used by the optimizer.
*/
@@ -340,6 +342,7 @@ final class ProcessCachedOptimizerRecord {
}
@GuardedBy("mProcLock")
+ @NeverCompile
void dump(PrintWriter pw, String prefix, long nowUptime) {
pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
pw.print(" lastCompactProfile=");
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 48a9d6af0df4..6779f7a37f20 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -351,6 +351,7 @@ public final class ProcessList {
// LMK_UPDATE_PROPS
// LMK_KILL_OCCURRED
// LMK_START_MONITORING
+ // LMK_BOOT_COMPLETED
static final byte LMK_TARGET = 0;
static final byte LMK_PROCPRIO = 1;
static final byte LMK_PROCREMOVE = 2;
@@ -361,6 +362,7 @@ public final class ProcessList {
static final byte LMK_UPDATE_PROPS = 7;
static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event
static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier
+ static final byte LMK_BOOT_COMPLETED = 10;
// Low Memory Killer Daemon command codes.
// These must be kept in sync with async_event_type definitions in lmkd.h
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b93908974a42..08165275757e 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -745,6 +745,9 @@ class ProcessRecord implements WindowProcessListener {
mOnewayThread = thread;
}
mWindowProcessController.setThread(thread);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.add(this);
+ }
}
@GuardedBy({"mService", "mProcLock"})
@@ -752,9 +755,19 @@ class ProcessRecord implements WindowProcessListener {
mThread = null;
mOnewayThread = null;
mWindowProcessController.setThread(null);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.remove(this);
+ }
mProfile.onProcessInactive(tracker);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean useFifoUiScheduling() {
+ return mService.mUseFifoUiScheduling
+ || (mService.mAllowSpecifiedFifoScheduling
+ && mWindowProcessController.useFifoUiScheduling());
+ }
+
@GuardedBy("mService")
int getDyingPid() {
return mDyingPid;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 60a8b50feeab..dd4cee47bee9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -246,6 +246,7 @@ class UserController implements Handler.Callback {
*
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
*/
@GuardedBy("mLock")
private int mMaxRunningUsers;
@@ -578,7 +579,8 @@ class UserController implements Handler.Callback {
// from outside.
Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
currentlyRunningLru.size(), userId);
- if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ if (stopUsersLU(userId,
+ /* stopProfileRegardlessOfParent= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
// Technically, stopUsersLU can remove more than one user when stopping a parent.
@@ -875,7 +877,7 @@ class UserController implements Handler.Callback {
Slogf.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
// Pre-created user was started right after creation so services could properly
// intialize it; it should be stopped right away as it's not really a "real" user.
- stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ stopUser(userInfo.id, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -930,7 +932,7 @@ class UserController implements Handler.Callback {
}
int restartUser(final int userId, @UserStartMode int userStartMode) {
- return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ return stopUser(userId, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
@@ -966,18 +968,24 @@ class UserController implements Handler.Callback {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, /* force= */ true, /* allowDelayedLocking= */
- false, /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ return stopUsersLU(userId, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== ActivityManager.USER_OP_SUCCESS;
}
}
- int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ int stopUser(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUser(userId, true, allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
+ int stopUser(final int userId,
+ final boolean stopProfileRegardlessOfParent, final boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("UserController"
- + (force ? "-force" : "")
+ + (stopProfileRegardlessOfParent ? "-stopProfileRegardlessOfParent" : "")
+ (allowDelayedLocking ? "-allowDelayedLocking" : "")
+ (stopUserCallback != null ? "-withStopUserCallback" : "")
+ "-" + userId + "-[stopUser]");
@@ -987,20 +995,32 @@ class UserController implements Handler.Callback {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
+ return stopUsersLU(userId, stopProfileRegardlessOfParent, allowDelayedLocking,
+ stopUserCallback, keyEvictedCallback);
}
} finally {
t.traceEnd();
}
}
+ /** Stops the user along with its profiles. */
+ @GuardedBy("mLock")
+ private int stopUsersLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ true,
+ allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
/**
* Stops the user along with its profiles. The method calls
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
+ private int stopUsersLU(final int userId,
+ boolean stopProfileRegardlessOfParent, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -1008,34 +1028,28 @@ class UserController implements Handler.Callback {
if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
- // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
- final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
- if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
- if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
- return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ if (!stopProfileRegardlessOfParent) {
+ final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
+ if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId))) {
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
}
}
- TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- int[] usersToStop = getUsersToStopLU(userId);
- // If one of related users is system or current, no related users should be stopped
+ final int[] usersToStop = getUsersToStopLU(userId);
+
+ // Final safety check: abort if one of the users we would plan to stop must not be stopped.
+ // This should be impossible in the current code, but just in case.
for (int relatedUserId : usersToStop) {
if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId);
- }
- // We still need to stop the requested user if it's a force stop.
- if (force) {
- Slogf.i(TAG,
- "Force stop user " + userId + ". Related users will not be stopped");
- t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]");
- stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
- t.traceEnd();
- return USER_OP_SUCCESS;
- }
+ Slogf.e(TAG, "Cannot stop user %d because it is related to user %d. ",
+ userId, relatedUserId);
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
}
}
+
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog();
if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]");
@@ -1304,8 +1318,8 @@ class UserController implements Handler.Callback {
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
- t.traceBegin("forceStopUser-" + userId + "-[stopUser]");
- forceStopUser(userId, "finish user");
+ t.traceBegin("stopPackagesOfStoppedUser-" + userId + "-[stopUser]");
+ stopPackagesOfStoppedUser(userId, "finish user");
t.traceEnd();
}
@@ -1516,8 +1530,8 @@ class UserController implements Handler.Callback {
return userIds.toArray();
}
- private void forceStopUser(@UserIdInt int userId, String reason) {
- if (DEBUG_MU) Slogf.i(TAG, "forceStopUser(%d): %s", userId, reason);
+ private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
+ if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
mInjector.activityManagerForceStopPackage(userId, reason);
if (mInjector.getUserManager().isPreCreated(userId)) {
// Don't fire intent for precreated.
@@ -1566,8 +1580,7 @@ class UserController implements Handler.Callback {
// This is a user to be stopped.
Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
synchronized (mLock) {
- stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
}
}
}
@@ -2259,8 +2272,7 @@ class UserController implements Handler.Callback {
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
if (hasRestriction || shouldStopUserOnSwitch()) {
Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
- stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
return;
}
}
@@ -2275,7 +2287,8 @@ class UserController implements Handler.Callback {
Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
synchronized (mLock) {
stopUsersLU(profileUserId,
- /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+ /* stopProfileRegardlessOfParent= */ false,
+ /* allowDelayedLocking= */ false, null, null);
}
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index fd847f11157f..e1ccf4d2a362 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.am"
+container: "system"
flag {
name: "oomadjuster_correctness_rewrite"
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index fb627854209d..debd9d0f0c83 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2671,14 +2671,10 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- public CheckOpsDelegate getAppOpsServiceDelegate() {
- synchronized (AppOpsService.this) {
- final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
- return (dispatcher != null) ? dispatcher.getCheckOpsDelegate() : null;
- }
- }
-
- public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
+ /**
+ * Sets the CheckOpDelegate
+ */
+ public void setCheckOpsDelegate(CheckOpsDelegate delegate) {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
final CheckOpsDelegate policy = (oldDispatcher != null) ? oldDispatcher.mPolicy : null;
@@ -7157,10 +7153,6 @@ public class AppOpsService extends IAppOpsService.Stub {
mCheckOpsDelegate = checkOpsDelegate;
}
- public @NonNull CheckOpsDelegate getCheckOpsDelegate() {
- return mCheckOpsDelegate;
- }
-
public int checkOperation(int code, int uid, String packageName,
@Nullable String attributionTag, int virtualDeviceId, boolean raw) {
if (mPolicy != null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 951f676dd098..77654d4a5413 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1517,8 +1517,9 @@ public class AudioDeviceBroker {
sendLMsgNoDelay(MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
}
- /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState) {
- sendLMsgNoDelay(MSG_L_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, deviceState);
+ /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) {
+ sendILMsgNoDelay(
+ MSG_IL_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, initSA ? 1 : 0, deviceState);
}
/*package*/ static final class CommunicationDeviceInfo {
@@ -1820,18 +1821,17 @@ public class AudioDeviceBroker {
+ "received with null profile proxy: "
+ btInfo)).printLog(TAG));
} else {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ final Pair<Integer, Boolean> codecAndChanged =
mBtHelper.getCodecWithFallback(btInfo.mDevice,
btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_SET_BT_ACTIVE_DEVICE");
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
- (btInfo.mProfile
- != BluetoothProfile.LE_AUDIO
+ mDeviceInventory.onSetBtActiveDevice(btInfo, codecAndChanged.first,
+ (btInfo.mProfile != BluetoothProfile.LE_AUDIO
|| btInfo.mIsLeOutput)
- ? mAudioService.getBluetoothContextualVolumeStream()
- : AudioSystem.STREAM_DEFAULT);
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile
== BluetoothProfile.HEARING_AID) {
@@ -1866,13 +1866,13 @@ public class AudioDeviceBroker {
break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
+ final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(
+ btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(
- btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+ mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ codecAndChanged.first, codecAndChanged.second,
+ BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
} break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -2049,8 +2049,8 @@ public class AudioDeviceBroker {
}
} break;
- case MSG_L_UPDATED_ADI_DEVICE_STATE:
- mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+ case MSG_IL_UPDATED_ADI_DEVICE_STATE:
+ mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj, msg.arg1 == 1);
break;
default:
@@ -2137,7 +2137,7 @@ public class AudioDeviceBroker {
private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56;
private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
- private static final int MSG_L_UPDATED_ADI_DEVICE_STATE = 59;
+ private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 14428c41ef26..f38b38154bc3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -173,7 +173,7 @@ public class AudioDeviceInventory {
if (ads.getAudioDeviceCategory() != category && (userDefined
|| category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
mDeviceBroker.postPersistAudioDeviceSettings();
}
mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads);
@@ -186,7 +186,7 @@ public class AudioDeviceInventory {
mDeviceInventory.put(ads.getDeviceId(), ads);
checkDeviceInventorySize_l();
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, true /*initSA*/);
mDeviceBroker.postPersistAudioDeviceSettings();
}
}
@@ -216,7 +216,7 @@ public class AudioDeviceInventory {
checkDeviceInventorySize_l();
}
if (updatedCategory.get()) {
- mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
+ mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/);
}
mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
}
@@ -318,7 +318,7 @@ public class AudioDeviceInventory {
}
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeBleDeviceInInventory synced device pair ads1="
+ updatedDevice + " ads2=" + ads2).printLog(TAG));
@@ -339,7 +339,7 @@ public class AudioDeviceInventory {
}
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeBleDeviceInInventory synced device pair ads1="
+ updatedDevice + " peer ads2=" + ads2).printLog(TAG));
@@ -364,7 +364,7 @@ public class AudioDeviceInventory {
}
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeDeviceProfilesInInventory synced device pair ads1="
+ updatedDevice + " ads2=" + ads).printLog(TAG));
@@ -868,7 +868,8 @@ public class AudioDeviceInventory {
@GuardedBy("mDeviceBroker.mDeviceStateLock")
/*package*/ void onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
+ boolean codecChanged, int event) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
@@ -916,14 +917,12 @@ public class AudioDeviceInventory {
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
- boolean codecChange = false;
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) {
- if (di.mDeviceCodecFormat != codec) {
+ if (codecChanged) {
di.mDeviceCodecFormat = codec;
mConnectedDevices.replace(key, di);
- codecChange = true;
final int res = mAudioSystem.handleDeviceConfigChange(
btInfo.mAudioSystemDevice, address,
BtHelper.getName(btDevice), codec);
@@ -947,7 +946,7 @@ public class AudioDeviceInventory {
}
}
}
- if (!codecChange) {
+ if (!codecChanged) {
updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 85acf707677a..570d4e9857e6 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -53,6 +53,12 @@ class AudioManagerShellCommand extends ShellCommand {
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-volume":
+ return setVolume();
+ case "adj-mute":
+ return adjMute();
+ case "adj-unmute":
+ return adjUnmute();
}
return 0;
}
@@ -78,6 +84,12 @@ class AudioManagerShellCommand extends ShellCommand {
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
+ pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
+ pw.println(" adj-mute STREAM_TYPE");
+ pw.println(" mutes the STREAM_TYPE");
+ pw.println(" adj-unmute STREAM_TYPE");
+ pw.println(" unmutes the STREAM_TYPE");
}
private int setSurroundFormatEnabled() {
@@ -216,4 +228,54 @@ class AudioManagerShellCommand extends ShellCommand {
getOutPrintWriter().println("Reset sound dose momentary exposure timeout");
return 0;
}
+
+ private int setVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int index = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.setStreamVolume("
+ + stream + ", " + index + ", 0)");
+ am.setStreamVolume(stream, index, 0);
+ return 0;
+ }
+
+ private int adjMute() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", AudioManager.ADJUST_MUTE, 0)");
+ am.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+ return 0;
+ }
+
+ private int adjUnmute() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", AudioManager.ADJUST_UNMUTE, 0)");
+ am.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+ return 0;
+ }
+
+ private int readIntArg() throws IllegalArgumentException {
+ String argText = getNextArg();
+
+ if (argText == null) {
+ getErrPrintWriter().println("Error: no argument provided");
+ throw new IllegalArgumentException("No argument provided");
+ }
+
+ int argIntVal = Integer.MIN_VALUE;
+ try {
+ argIntVal = Integer.parseInt(argText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: wrong format for argument " + argText);
+ throw new IllegalArgumentException("Wrong format for argument " + argText);
+ }
+
+ return argIntVal;
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ed58c4033c4c..5ba0af402e4d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -47,6 +47,7 @@ import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
+import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -4544,6 +4545,8 @@ public class AudioService extends IAudioService.Stub
+ setStreamVolumeOrder());
pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+ roForegroundAudioControl());
+ pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:"
+ + vgsVssSyncMuteOrder());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -8317,13 +8320,23 @@ public class AudioService extends IAudioService.Stub
synced = true;
continue;
}
+ if (vgsVssSyncMuteOrder()) {
+ if ((isMuted() != streamMuted) && isVssMuteBijective(
+ stream)) {
+ mStreamStates[stream].mute(isMuted(),
+ "VGS.applyAllVolumes#1");
+ }
+ }
if (indexForStream != index) {
mStreamStates[stream].setIndex(index * 10, device, caller,
true /*hasModifyAudioSettings*/);
}
- if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
- mStreamStates[stream].mute(isMuted(),
- "VGS.applyAllVolumes#1");
+ if (!vgsVssSyncMuteOrder()) {
+ if ((isMuted() != streamMuted) && isVssMuteBijective(
+ stream)) {
+ mStreamStates[stream].mute(isMuted(),
+ "VGS.applyAllVolumes#1");
+ }
}
}
}
@@ -8855,6 +8868,7 @@ public class AudioService extends IAudioService.Stub
boolean changed;
int oldIndex;
final boolean isCurrentDevice;
+ final StringBuilder aliasStreamIndexes = new StringBuilder();
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
@@ -8881,13 +8895,17 @@ public class AudioService extends IAudioService.Stub
(changed || !aliasStreamState.hasIndexForDevice(device))) {
final int scaledIndex =
rescaleIndex(aliasIndex, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller,
- hasModifyAudioSettings);
+ boolean changedAlias = aliasStreamState.setIndex(scaledIndex, device,
+ caller, hasModifyAudioSettings);
if (isCurrentDevice) {
- aliasStreamState.setIndex(scaledIndex,
+ changedAlias |= aliasStreamState.setIndex(scaledIndex,
getDeviceForStream(streamType), caller,
hasModifyAudioSettings);
}
+ if (changedAlias) {
+ aliasStreamIndexes.append(AudioSystem.streamToString(streamType))
+ .append(":").append((scaledIndex + 5) / 10).append(" ");
+ }
}
}
// Mirror changes in SPEAKER ringtone volume on SCO when
@@ -8927,8 +8945,15 @@ public class AudioService extends IAudioService.Stub
oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
- AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex));
+ if (mStreamType == mStreamVolumeAlias[mStreamType]) {
+ String aliasStreamIndexesString = "";
+ if (!aliasStreamIndexes.isEmpty()) {
+ aliasStreamIndexesString =
+ " aliased streams: " + aliasStreamIndexes;
+ }
+ AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+ mStreamType, aliasStreamIndexesString, index, oldIndex));
+ }
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
}
}
@@ -11271,7 +11296,8 @@ public class AudioService extends IAudioService.Stub
mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
mDeviceBroker.postPersistAudioDeviceSettings();
- mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
+ mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(),
+ false /* initState */);
mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES);
}
@@ -11342,11 +11368,11 @@ public class AudioService extends IAudioService.Stub
/** Update the sound dose and spatializer state based on the new AdiDeviceState. */
@VisibleForTesting(visibility = PACKAGE)
- public void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
+ public void onUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) {
if (deviceState == null) {
return;
}
- mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
+ mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), initSA);
mSoundDoseHelper.setAudioDeviceCategory(deviceState.getDeviceAddress(),
deviceState.getInternalDeviceType(),
deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES);
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3b1c011f09c0..749044e05cbc 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -151,13 +151,13 @@ public class AudioServiceEvents {
static final class VolChangedBroadcastEvent extends EventLogger.Event {
final int mStreamType;
- final int mAliasStreamType;
+ final String mAliasStreamIndexes;
final int mIndex;
final int mOldIndex;
- VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) {
+ VolChangedBroadcastEvent(int stream, String aliasIndexes, int index, int oldIndex) {
mStreamType = stream;
- mAliasStreamType = alias;
+ mAliasStreamIndexes = aliasIndexes;
mIndex = index;
mOldIndex = oldIndex;
}
@@ -167,8 +167,8 @@ public class AudioServiceEvents {
return new StringBuilder("sending VOLUME_CHANGED stream:")
.append(AudioSystem.streamToString(mStreamType))
.append(" index:").append(mIndex)
- .append(" (was:").append(mOldIndex)
- .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .append(" (was:").append(mOldIndex).append(")")
+ .append(mAliasStreamIndexes)
.toString();
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index f3a5fdb3cacc..edeabdc5243c 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -98,9 +98,16 @@ public class BtHelper {
private @Nullable BluetoothLeAudio mLeAudio;
+ private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig;
+
// Reference to BluetoothA2dp to query for AbsoluteVolume.
private @Nullable BluetoothA2dp mA2dp;
+ private @Nullable BluetoothCodecConfig mA2dpCodecConfig;
+
+ private @AudioSystem.AudioFormatNativeEnumForBtCodec
+ int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+
// If absolute volume is supported in AVRCP device
private boolean mAvrcpAbsVolSupported = false;
@@ -265,12 +272,15 @@ public class BtHelper {
}
}
- /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec(
+ private synchronized Pair<Integer, Boolean> getCodec(
@NonNull BluetoothDevice device, @AudioService.BtProfile int profile) {
+
switch (profile) {
case BluetoothProfile.A2DP: {
+ boolean changed = mA2dpCodecConfig != null;
if (mA2dp == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothCodecStatus btCodecStatus = null;
try {
@@ -279,17 +289,24 @@ public class BtHelper {
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
if (btCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
- return AudioSystem.bluetoothA2dpCodecToAudioFormat(btCodecConfig.getCodecType());
+ changed = !btCodecConfig.equals(mA2dpCodecConfig);
+ mA2dpCodecConfig = btCodecConfig;
+ return new Pair<>(AudioSystem.bluetoothA2dpCodecToAudioFormat(
+ btCodecConfig.getCodecType()), changed);
}
case BluetoothProfile.LE_AUDIO: {
+ boolean changed = mLeAudioCodecConfig != null;
if (mLeAudio == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothLeAudioCodecStatus btLeCodecStatus = null;
int groupId = mLeAudio.getGroupId(device);
@@ -299,42 +316,54 @@ public class BtHelper {
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btLeCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothLeAudioCodecConfig btLeCodecConfig =
btLeCodecStatus.getOutputCodecConfig();
if (btLeCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
- return AudioSystem.bluetoothLeCodecToAudioFormat(btLeCodecConfig.getCodecType());
+ changed = !btLeCodecConfig.equals(mLeAudioCodecConfig);
+ mLeAudioCodecConfig = btLeCodecConfig;
+ return new Pair<>(AudioSystem.bluetoothLeCodecToAudioFormat(
+ btLeCodecConfig.getCodecType()), changed);
+ }
+ case BluetoothProfile.LE_AUDIO_BROADCAST: {
+ // We assume LC3 for LE Audio broadcast codec as there is no API to get the codec
+ // config on LE Broadcast profile proxy.
+ boolean changed = mLeAudioBroadcastCodec != AudioSystem.AUDIO_FORMAT_LC3;
+ mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_LC3;
+ return new Pair<>(mLeAudioBroadcastCodec, changed);
}
default:
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
}
}
- /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec
- int getCodecWithFallback(
- @NonNull BluetoothDevice device, @AudioService.BtProfile int profile,
- boolean isLeOutput, @NonNull String source) {
+ /*package*/ synchronized Pair<Integer, Boolean>
+ getCodecWithFallback(@NonNull BluetoothDevice device,
+ @AudioService.BtProfile int profile,
+ boolean isLeOutput, @NonNull String source) {
// For profiles other than A2DP and LE Audio output, the audio codec format must be
// AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format
// only if audio HW module selection based on format is supported for the device type.
if (!(profile == BluetoothProfile.A2DP
|| (isLeOutput && ((profile == BluetoothProfile.LE_AUDIO)
|| (profile == BluetoothProfile.LE_AUDIO_BROADCAST))))) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
}
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec =
+ Pair<Integer, Boolean> codecAndChanged =
getCodec(device, profile);
- if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) {
+ if (codecAndChanged.first == AudioSystem.AUDIO_FORMAT_DEFAULT) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"getCodec DEFAULT from " + source + " fallback to "
+ (profile == BluetoothProfile.A2DP ? "SBC" : "LC3")));
- return profile == BluetoothProfile.A2DP
- ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3;
+ return new Pair<>(profile == BluetoothProfile.A2DP
+ ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true);
}
- return codec;
+ return codecAndChanged;
}
// @GuardedBy("mDeviceBroker.mSetModeLock")
@@ -539,15 +568,19 @@ public class BtHelper {
break;
case BluetoothProfile.A2DP:
mA2dp = null;
+ mA2dpCodecConfig = null;
break;
case BluetoothProfile.HEARING_AID:
mHearingAid = null;
break;
case BluetoothProfile.LE_AUDIO:
mLeAudio = null;
+ mLeAudioCodecConfig = null;
break;
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.LE_AUDIO_BROADCAST:
+ mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+ break;
+ case BluetoothProfile.A2DP_SINK:
// nothing to do in BtHelper
break;
default:
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 08da32e8831c..28af22214f8d 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -235,6 +235,10 @@ public final class PlaybackActivityMonitor
public int trackPlayer(PlayerBase.PlayerIdCard pic) {
final int newPiid = AudioSystem.newAudioPlayerId();
if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
+ if (newPiid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "invalid piid assigned from AudioSystem");
+ return newPiid;
+ }
final AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -365,15 +369,14 @@ public final class PlaybackActivityMonitor
sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_II_UPDATE_PORT_EVENT, eventValue, piid));
+ mPortIdToPiid.put(eventValue, piid);
return;
} else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
sEventLogger.enqueue(new EventLogger.StringEvent(
- "not starting piid:" + piid + " ,is banned"));
+ "not starting piid:" + piid + ", is banned"));
return;
}
}
@@ -429,16 +432,12 @@ public final class PlaybackActivityMonitor
synchronized (mPlayerLock) {
int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
if (piid == PLAYER_PIID_INVALID) {
- if (DEBUG) {
- Log.v(TAG, "No piid assigned for invalid/internal port id " + portId);
- }
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
return;
}
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
- if (DEBUG) {
- Log.v(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
- }
+ Log.w(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
return;
}
@@ -470,11 +469,18 @@ public final class PlaybackActivityMonitor
public void releasePlayer(int piid, int binderUid) {
if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
boolean change = false;
+ if (piid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "Received releasePlayer with invalid piid: " + piid);
+ sEventLogger.enqueue(new EventLogger.StringEvent("releasePlayer with invalid piid:"
+ + piid + ", uid:" + binderUid));
+ return;
+ }
+
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
sEventLogger.enqueue(new EventLogger.StringEvent(
- "releasing player piid:" + piid));
+ "releasing player piid:" + piid + ", uid:" + binderUid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
mFadeOutManager.removeReleased(apc);
@@ -484,8 +490,10 @@ public final class PlaybackActivityMonitor
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
// remove all port ids mapped to the released player
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_I_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+ int portIdx;
+ while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
+ mPortIdToPiid.removeAt(portIdx);
+ }
if (change && mDoNotLogPiidList.contains(piid)) {
// do not dispatch a change for a "do not log" player
@@ -1609,14 +1617,6 @@ public final class PlaybackActivityMonitor
private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
/**
- * assign new port id to piid
- * args:
- * msg.arg1: port id
- * msg.arg2: piid
- */
- private static final int MSG_II_UPDATE_PORT_EVENT = 2;
-
- /**
* event for player getting muted
* args:
* msg.arg1: piid
@@ -1624,14 +1624,7 @@ public final class PlaybackActivityMonitor
* msg.obj: extras describing the mute reason
* type: PersistableBundle
*/
- private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 3;
-
- /**
- * clear all ports assigned to a given piid
- * args:
- * msg.arg1: the piid
- */
- private static final int MSG_I_CLEAR_PORTS_FOR_PIID = 4;
+ private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 2;
/**
* event for player reporting playback format and spatialization status
@@ -1641,7 +1634,7 @@ public final class PlaybackActivityMonitor
* msg.obj: extras describing the sample rate, channel mask, spatialized
* type: PersistableBundle
*/
- private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 5;
+ private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 3;
private void initEventHandler() {
mEventThread = new HandlerThread(TAG);
@@ -1660,11 +1653,6 @@ public final class PlaybackActivityMonitor
mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
break;
- case MSG_II_UPDATE_PORT_EVENT:
- synchronized (mPlayerLock) {
- mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
- }
- break;
case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
// TODO: replace PersistableBundle with own struct
PersistableBundle extras = (PersistableBundle) msg.obj;
@@ -1680,10 +1668,7 @@ public final class PlaybackActivityMonitor
sEventLogger.enqueue(
new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
- final AudioPlaybackConfiguration apc;
- synchronized (mPlayerLock) {
- apc = mPlayers.get(piid);
- }
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null || !apc.handleMutedEvent(eventValue)) {
break; // do not dispatch
}
@@ -1691,21 +1676,6 @@ public final class PlaybackActivityMonitor
}
break;
- case MSG_I_CLEAR_PORTS_FOR_PIID:
- int piid = msg.arg1;
- if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
- Log.w(TAG, "Received clear ports with invalid piid");
- break;
- }
-
- synchronized (mPlayerLock) {
- int portIdx;
- while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
- mPortIdToPiid.removeAt(portIdx);
- }
- }
- break;
-
case MSG_IIL_UPDATE_PLAYER_FORMAT:
final PersistableBundle formatExtras = (PersistableBundle) msg.obj;
if (formatExtras == null) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 3b5fa7f00891..38fa79f7f44a 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -295,11 +295,11 @@ public class SpatializerHelper {
// could have been called another time after boot in case of audioserver restart
addCompatibleAudioDevice(
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
+ false /*forceEnable*/, false /*forceInit*/);
// not force-enabling as this device might already be in the device list
addCompatibleAudioDevice(
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
+ false /*forceEnable*/, false /*forceInit*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -526,7 +526,7 @@ public class SpatializerHelper {
}
synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
- addCompatibleAudioDevice(ada, true /*forceEnable*/);
+ addCompatibleAudioDevice(ada, true /*forceEnable*/, false /*forceInit*/);
}
/**
@@ -540,7 +540,7 @@ public class SpatializerHelper {
*/
@GuardedBy("this")
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
- boolean forceEnable) {
+ boolean forceEnable, boolean forceInit) {
if (!isDeviceCompatibleWithSpatializationModes(ada)) {
return;
}
@@ -548,6 +548,9 @@ public class SpatializerHelper {
final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada);
AdiDeviceState updatedDevice = null; // non-null on update.
if (deviceState != null) {
+ if (forceInit) {
+ initSAState(deviceState);
+ }
if (forceEnable && !deviceState.isSAEnabled()) {
updatedDevice = deviceState;
updatedDevice.setSAEnabled(true);
@@ -756,10 +759,10 @@ public class SpatializerHelper {
}
}
- synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada) {
+ synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada, boolean initState) {
final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada);
if (isAvailableForAdiDeviceState(deviceState)) {
- addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled());
+ addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled(), initState);
setHeadTrackerEnabled(deviceState.isHeadTrackerEnabled(), ada);
} else {
removeCompatibleAudioDevice(ada);
diff --git a/services/core/java/com/android/server/biometrics/Android.bp b/services/core/java/com/android/server/biometrics/Android.bp
index 6cbe4adbcdbe..ed5de0305863 100644
--- a/services/core/java/com/android/server/biometrics/Android.bp
+++ b/services/core/java/com/android/server/biometrics/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "biometrics_flags",
package: "com.android.server.biometrics",
+ container: "system",
srcs: [
"biometrics.aconfig",
],
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 3d4801b3e9aa..c03b3b86ad03 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -56,7 +56,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.IBinder;
import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -111,7 +111,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
@NonNull private final BiometricContext mBiometricContext;
private final IStatusBarService mStatusBarService;
@VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
- private final KeyStore mKeyStore;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final Random mRandom;
private final ClientDeathReceiver mClientDeathReceiver;
final PreAuthInfo mPreAuthInfo;
@@ -158,7 +158,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@NonNull IBiometricSysuiReceiver sysuiReceiver,
- @NonNull KeyStore keystore,
+ @NonNull KeyStoreAuthorization keyStoreAuthorization,
@NonNull Random random,
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@@ -172,8 +172,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
@NonNull PromptInfo promptInfo,
boolean debugEnabled,
@NonNull List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties) {
- this(context, biometricContext, statusBarService, sysuiReceiver, keystore, random,
- clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
+ this(context, biometricContext, statusBarService, sysuiReceiver, keyStoreAuthorization,
+ random, clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
sensorReceiver, clientReceiver, opPackageName, promptInfo, debugEnabled,
fingerprintSensorProperties, BiometricFrameworkStatsLogger.getInstance());
}
@@ -183,7 +183,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@NonNull IBiometricSysuiReceiver sysuiReceiver,
- @NonNull KeyStore keystore,
+ @NonNull KeyStoreAuthorization keyStoreAuthorization,
@NonNull Random random,
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@@ -203,7 +203,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mBiometricContext = biometricContext;
mStatusBarService = statusBarService;
mSysuiReceiver = sysuiReceiver;
- mKeyStore = keystore;
+ mKeyStoreAuthorization = keyStoreAuthorization;
mRandom = random;
mClientDeathReceiver = clientDeathReceiver;
mPreAuthInfo = preAuthInfo;
@@ -814,14 +814,14 @@ public final class AuthSession implements IBinder.DeathRecipient {
switch (reason) {
case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
if (credentialAttestation != null) {
- mKeyStore.addAuthToken(credentialAttestation);
+ mKeyStoreAuthorization.addAuthToken(credentialAttestation);
} else {
Slog.e(TAG, "credentialAttestation is null");
}
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
if (mTokenEscrow != null) {
- final int result = mKeyStore.addAuthToken(mTokenEscrow);
+ final int result = mKeyStoreAuthorization.addAuthToken(mTokenEscrow);
Slog.d(TAG, "addAuthToken: " + result);
} else {
Slog.e(TAG, "mTokenEscrow is null");
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 894b4d5d0b36..3737d6fb4f3f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -65,15 +65,11 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Authorization;
import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
-import android.security.authorization.ResponseCode;
+import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -123,11 +119,9 @@ public class BiometricService extends SystemService {
@VisibleForTesting
IStatusBarService mStatusBarService;
@VisibleForTesting
- KeyStore mKeyStore;
- @VisibleForTesting
ITrustManager mTrustManager;
@VisibleForTesting
- IKeystoreAuthorization mKeystoreAuthorization;
+ KeyStoreAuthorization mKeyStoreAuthorization;
@VisibleForTesting
IGateKeeperService mGateKeeper;
@@ -674,19 +668,7 @@ public class BiometricService extends SystemService {
int[] authTypesArray = hardwareAuthenticators.stream()
.mapToInt(Integer::intValue)
.toArray();
- try {
- return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error getting last auth time: " + e);
- return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
- } catch (ServiceSpecificException e) {
- // This is returned when the feature flag test fails in keystore2
- if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
- throw new UnsupportedOperationException();
- }
-
- return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
- }
+ return mKeyStoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@@ -1011,8 +993,8 @@ public class BiometricService extends SystemService {
return ActivityManager.getService();
}
- public IKeystoreAuthorization getKeystoreAuthorizationService() {
- return Authorization.getService();
+ public KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
}
public IGateKeeperService getGateKeeperService() {
@@ -1036,10 +1018,6 @@ public class BiometricService extends SystemService {
return new SettingObserver(context, handler, callbacks);
}
- public KeyStore getKeyStore() {
- return KeyStore.getInstance();
- }
-
/**
* Allows to enable/disable debug logs.
*/
@@ -1138,7 +1116,7 @@ public class BiometricService extends SystemService {
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
mBiometricCameraManager = injector.getBiometricCameraManager(context);
- mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mGateKeeper = injector.getGateKeeperService();
mBiometricNotificationLogger = injector.getNotificationLogger();
@@ -1159,7 +1137,6 @@ public class BiometricService extends SystemService {
@Override
public void onStart() {
- mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
mTrustManager = mInjector.getTrustManager();
mInjector.publishBinderService(this, mImpl);
@@ -1481,7 +1458,7 @@ public class BiometricService extends SystemService {
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
mAuthSession = new AuthSession(getContext(), mBiometricContext, mStatusBarService,
- createSysuiReceiver(requestId), mKeyStore, mRandom,
+ createSysuiReceiver(requestId), mKeyStoreAuthorization, mRandom,
createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
operationId, userId, createBiometricSensorReceiver(requestId), receiver,
opPackageName, promptInfo, debugEnabled,
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b12d831ffe24..7a9491e44cd7 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.biometrics"
+container: "system"
flag {
name: "face_vhal_feature"
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 62c21cf36f69..4fa8741c867e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -30,7 +30,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricRequestConstants;
import android.os.IBinder;
import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import android.util.EventLog;
import android.util.Slog;
@@ -255,7 +255,7 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
// For BP, BiometricService will add the authToken to Keystore.
if (!isBiometricPrompt() && mIsStrongBiometric) {
- final int result = KeyStore.getInstance().addAuthToken(byteToken);
+ final int result = KeyStoreAuthorization.getInstance().addAuthToken(byteToken);
if (result != 0) {
Slog.d(TAG, "Error adding auth token : " + result);
} else {
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 3ede0a2597d9..028b9b0bcbc0 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -26,7 +26,7 @@ import com.android.server.SystemService;
import java.util.ArrayList;
-public class BroadcastRadioService extends SystemService {
+public final class BroadcastRadioService extends SystemService {
private final IRadioService mServiceImpl;
public BroadcastRadioService(Context context) {
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 42b2682cf530..16514fa813dc 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -52,7 +52,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
private static final List<String> SERVICE_NAMES = Arrays.asList(
IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab");
- private final BroadcastRadioServiceImpl mHalAidl;
+ private final BroadcastRadioServiceImpl mAidlHalClient;
private final BroadcastRadioService mService;
/**
@@ -77,14 +77,14 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
@VisibleForTesting
IRadioServiceAidlImpl(BroadcastRadioService service, BroadcastRadioServiceImpl halAidl) {
mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
- mHalAidl = Objects.requireNonNull(halAidl,
+ mAidlHalClient = Objects.requireNonNull(halAidl,
"Broadcast radio service implementation for AIDL HAL cannot be null");
}
@Override
public List<RadioManager.ModuleProperties> listModules() {
mService.enforcePolicyAccess();
- return mHalAidl.listModules();
+ return mAidlHalClient.listModules();
}
@Override
@@ -97,7 +97,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
- return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+ return mAidlHalClient.openSession(moduleId, bandConfig, withAudio, callback);
}
@Override
@@ -110,7 +110,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
Objects.requireNonNull(listener, "Announcement listener cannot be null");
mService.enforcePolicyAccess();
- return mHalAidl.addAnnouncementListener(enabledTypes, listener);
+ return mAidlHalClient.addAnnouncementListener(enabledTypes, listener);
}
@Override
@@ -126,10 +126,10 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
radioPrintWriter.printf("BroadcastRadioService\n");
radioPrintWriter.increaseIndent();
- radioPrintWriter.printf("AIDL HAL:\n");
+ radioPrintWriter.printf("AIDL HAL client:\n");
radioPrintWriter.increaseIndent();
- mHalAidl.dumpInfo(radioPrintWriter);
+ mAidlHalClient.dumpInfo(radioPrintWriter);
radioPrintWriter.decreaseIndent();
radioPrintWriter.decreaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index bc72a4be18be..ab083429a200 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -49,8 +49,8 @@ import java.util.OptionalInt;
final class IRadioServiceHidlImpl extends IRadioService.Stub {
private static final String TAG = "BcRadioSrvHidl";
- private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1;
- private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2;
+ private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1Client;
+ private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2Client;
private final Object mLock = new Object();
@@ -61,10 +61,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
IRadioServiceHidlImpl(BroadcastRadioService service) {
mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");
- mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
- mV1Modules = mHal1.loadModules();
+ mHal1Client = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
+ mV1Modules = mHal1Client.loadModules();
OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
- mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
+ mHal2Client = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
max.isPresent() ? max.getAsInt() + 1 : 0);
}
@@ -73,17 +73,17 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
com.android.server.broadcastradio.hal1.BroadcastRadioService hal1,
com.android.server.broadcastradio.hal2.BroadcastRadioService hal2) {
mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
- mHal1 = Objects.requireNonNull(hal1,
+ mHal1Client = Objects.requireNonNull(hal1,
"Broadcast radio service implementation for HIDL 1 HAL cannot be null");
- mV1Modules = mHal1.loadModules();
- mHal2 = Objects.requireNonNull(hal2,
+ mV1Modules = mHal1Client.loadModules();
+ mHal2Client = Objects.requireNonNull(hal2,
"Broadcast radio service implementation for HIDL 2 HAL cannot be null");
}
@Override
public List<RadioManager.ModuleProperties> listModules() {
mService.enforcePolicyAccess();
- Collection<RadioManager.ModuleProperties> v2Modules = mHal2.listModules();
+ Collection<RadioManager.ModuleProperties> v2Modules = mHal2Client.listModules();
List<RadioManager.ModuleProperties> modules;
synchronized (mLock) {
modules = new ArrayList<>(mV1Modules.size() + v2Modules.size());
@@ -102,10 +102,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
mService.enforcePolicyAccess();
Objects.requireNonNull(callback, "Callback must not be null");
synchronized (mLock) {
- if (mHal2.hasModule(moduleId)) {
- return mHal2.openSession(moduleId, bandConfig, withAudio, callback);
+ if (mHal2Client.hasModule(moduleId)) {
+ return mHal2Client.openSession(moduleId, bandConfig, withAudio, callback);
} else {
- return mHal1.openTuner(moduleId, bandConfig, withAudio, callback);
+ return mHal1Client.openTuner(moduleId, bandConfig, withAudio, callback);
}
}
}
@@ -121,12 +121,12 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
mService.enforcePolicyAccess();
synchronized (mLock) {
- if (!mHal2.hasAnyModules()) {
+ if (!mHal2Client.hasAnyModules()) {
Slog.w(TAG, "There are no HAL 2.0 modules registered");
return new AnnouncementAggregator(listener, mLock);
}
- return mHal2.addAnnouncementListener(enabledTypes, listener);
+ return mHal2Client.addAnnouncementListener(enabledTypes, listener);
}
}
@@ -143,18 +143,18 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
radioPw.printf("BroadcastRadioService\n");
radioPw.increaseIndent();
- radioPw.printf("HAL1: %s\n", mHal1);
+ radioPw.printf("HAL1 client: %s\n", mHal1Client);
radioPw.increaseIndent();
synchronized (mLock) {
- radioPw.printf("Modules of HAL1: %s\n", mV1Modules);
+ radioPw.printf("Modules of HAL1 client: %s\n", mV1Modules);
}
radioPw.decreaseIndent();
- radioPw.printf("HAL2:\n");
+ radioPw.printf("HAL2 client:\n");
radioPw.increaseIndent();
- mHal2.dumpInfo(radioPw);
+ mHal2Client.dumpInfo(radioPw);
radioPw.decreaseIndent();
radioPw.decreaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
index 4b847a27c4de..c705ebe686f2 100644
--- a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
+++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
@@ -48,8 +48,8 @@ public final class RadioServiceUserController {
* @return foreground user id.
*/
public static int getCurrentUser() {
- final long identity = Binder.clearCallingIdentity();
int userId = UserHandle.USER_NULL;
+ final long identity = Binder.clearCallingIdentity();
try {
userId = ActivityManager.getCurrentUser();
} catch (RuntimeException e) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
index 219ee4c3229a..08ff6627785a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
@@ -18,12 +18,13 @@ package com.android.server.broadcastradio.hal1;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
import java.util.Map;
import java.util.Set;
-class Convert {
+final class Convert {
private static final String TAG = "BcRadio1Srv.Convert";
@@ -34,12 +35,12 @@ class Convert {
* side, which requires several separate java calls for each element.
*
* @param map map to convert.
- * @returns array (sized the same as map) of two-element string arrays
- * (first element is the key, second is value).
+ * @return array (sized the same as map) of two-element string arrays
+ * (first element is the key, second is value).
*/
static @NonNull String[][] stringMapToNative(@Nullable Map<String, String> map) {
if (map == null) {
- Slog.v(TAG, "map is null, returning zero-elements array");
+ Slogf.v(TAG, "map is null, returning zero-elements array");
return new String[0][0];
}
@@ -54,7 +55,7 @@ class Convert {
i++;
}
- Slog.v(TAG, "converted " + i + " element(s)");
+ Slogf.v(TAG, "converted " + i + " element(s)");
return arr;
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index 8e5f6b5b8624..7cac4091c583 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -26,7 +26,6 @@ import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -44,9 +43,9 @@ class Tuner extends ITuner.Stub {
private final long mNativeContext;
private final Object mLock = new Object();
- @NonNull private final TunerCallback mTunerCallback;
- @NonNull private final ITunerCallback mClientCallback;
- @NonNull private final IBinder.DeathRecipient mDeathRecipient;
+ private final TunerCallback mTunerCallback;
+ private final ITunerCallback mClientCallback;
+ private final IBinder.DeathRecipient mDeathRecipient;
private boolean mIsClosed = false;
private boolean mIsMuted = false;
@@ -122,7 +121,7 @@ class Tuner extends ITuner.Stub {
private boolean checkConfiguredLocked() {
if (mTunerCallback.isInitialConfigurationDone()) return true;
- Slog.w(TAG, "Initial configuration is still pending, skipping the operation");
+ Slogf.w(TAG, "Initial configuration is still pending, skipping the operation");
return false;
}
@@ -159,14 +158,14 @@ class Tuner extends ITuner.Stub {
checkNotClosedLocked();
if (mIsMuted == mute) return;
mIsMuted = mute;
- Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
+ Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
}
}
@Override
public boolean isMuted() {
if (!mWithAudio) {
- Slog.w(TAG, "Tuner did not request audio, pretending it was muted");
+ Slogf.w(TAG, "Tuner did not request audio, pretending it was muted");
return true;
}
synchronized (mLock) {
@@ -210,7 +209,7 @@ class Tuner extends ITuner.Stub {
if (selector == null) {
throw new IllegalArgumentException("The argument must not be a null pointer");
}
- Slog.i(TAG, "Tuning to " + selector);
+ Slogf.i(TAG, "Tuning to " + selector);
synchronized (mLock) {
checkNotClosedLocked();
if (!checkConfiguredLocked()) return;
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index aa43b7581fe7..e013643a812d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -25,7 +25,8 @@ import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
import java.util.List;
import java.util.Map;
@@ -42,8 +43,8 @@ class TunerCallback implements ITunerCallback {
*/
private final long mNativeContext;
- @NonNull private final Tuner mTuner;
- @NonNull private final ITunerCallback mClientCallback;
+ private final Tuner mTuner;
+ private final ITunerCallback mClientCallback;
private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>();
private boolean mInitialConfigurationDone = false;
@@ -76,7 +77,7 @@ class TunerCallback implements ITunerCallback {
try {
func.run();
} catch (RemoteException e) {
- Slog.e(TAG, "client died", e);
+ Slogf.e(TAG, "client died", e);
}
}
@@ -107,7 +108,7 @@ class TunerCallback implements ITunerCallback {
@Override
public void onTuneFailed(int result, ProgramSelector selector) {
- Slog.e(TAG, "Not applicable for HAL 1.x");
+ Slogf.e(TAG, "Not applicable for HAL 1.x");
}
@Override
@@ -160,7 +161,7 @@ class TunerCallback implements ITunerCallback {
try {
modified = mTuner.getProgramList(filter.getVendorFilter());
} catch (IllegalStateException ex) {
- Slog.d(TAG, "Program list not ready yet");
+ Slogf.d(TAG, "Program list not ready yet");
return;
}
Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet());
@@ -175,12 +176,12 @@ class TunerCallback implements ITunerCallback {
@Override
public void onConfigFlagUpdated(int flag, boolean value) {
- Slog.w(TAG, "Not applicable for HAL 1.x");
+ Slogf.w(TAG, "Not applicable for HAL 1.x");
}
@Override
public void onParametersUpdated(Map<String, String> parameters) {
- Slog.w(TAG, "Not applicable for HAL 1.x");
+ Slogf.w(TAG, "Not applicable for HAL 1.x");
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
index 85c13aee37c9..0327ee79d39c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -23,20 +23,20 @@ import android.hardware.radio.IAnnouncementListener;
import android.hardware.radio.ICloseHandle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
-public class AnnouncementAggregator extends ICloseHandle.Stub {
+public final class AnnouncementAggregator extends ICloseHandle.Stub {
private static final String TAG = "BcRadio2Srv.AnnAggr";
private final Object mLock;
- @NonNull private final IAnnouncementListener mListener;
+ private final IAnnouncementListener mListener;
private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
@GuardedBy("mLock")
@@ -77,14 +77,16 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
public void binderDied() {
try {
close();
- } catch (RemoteException ex) {}
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Cannot close Announcement aggregator for DeathRecipient");
+ }
}
}
private void onListUpdated() {
synchronized (mLock) {
if (mIsClosed) {
- Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+ Slogf.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
return;
}
List<Announcement> combined = new ArrayList<>();
@@ -94,7 +96,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
try {
mListener.onListUpdated(combined);
} catch (RemoteException ex) {
- Slog.e(TAG, "mListener.onListUpdated() failed: ", ex);
+ Slogf.e(TAG, "mListener.onListUpdated() failed: ", ex);
}
}
}
@@ -111,7 +113,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
try {
closeHandle = module.addAnnouncementListener(enabledTypes, watcher);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to add announcement listener", ex);
+ Slogf.e(TAG, "Failed to add announcement listener", ex);
return;
}
watcher.setCloseHandle(closeHandle);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 1e31f200fd47..3198842c1ff3 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -29,8 +29,8 @@ import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IHwBinder.DeathRecipient;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,7 +38,6 @@ import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -55,16 +54,17 @@ public final class BroadcastRadioService {
private int mNextModuleId;
@GuardedBy("mLock")
- private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>();
+ private final Map<String, Integer> mServiceNameToModuleIdMap = new ArrayMap<>();
// Map from module ID to RadioModule created by mServiceListener.onRegistration().
@GuardedBy("mLock")
- private final Map<Integer, RadioModule> mModules = new HashMap<>();
+ private final Map<Integer, RadioModule> mModules = new ArrayMap<>();
- private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
+ private final IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
@Override
public void onRegistration(String fqName, String serviceName, boolean preexisting) {
- Slog.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + ")");
+ Slogf.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting
+ + ")");
Integer moduleId;
synchronized (mLock) {
// If the service has been registered before, reuse its previous module ID.
@@ -75,13 +75,13 @@ public final class BroadcastRadioService {
moduleId = mNextModuleId;
}
- RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName);
- if (module == null) {
+ RadioModule radioModule = RadioModule.tryLoadingModule(moduleId, serviceName);
+ if (radioModule == null) {
return;
}
- Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
+ Slogf.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
+ " (HAL 2.0)");
- RadioModule prevModule = mModules.put(moduleId, module);
+ RadioModule prevModule = mModules.put(moduleId, radioModule);
if (prevModule != null) {
prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
}
@@ -92,7 +92,7 @@ public final class BroadcastRadioService {
}
try {
- module.getService().linkToDeath(mDeathRecipient, moduleId);
+ radioModule.getService().linkToDeath(mDeathRecipient, moduleId);
} catch (RemoteException ex) {
// Service has already died, so remove its entry from mModules.
mModules.remove(moduleId);
@@ -101,10 +101,10 @@ public final class BroadcastRadioService {
}
};
- private DeathRecipient mDeathRecipient = new DeathRecipient() {
+ private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void serviceDied(long cookie) {
- Slog.v(TAG, "serviceDied(" + cookie + ")");
+ Slogf.v(TAG, "serviceDied(" + cookie + ")");
synchronized (mLock) {
int moduleId = (int) cookie;
RadioModule prevModule = mModules.remove(moduleId);
@@ -114,7 +114,7 @@ public final class BroadcastRadioService {
for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
if (entry.getValue() == moduleId) {
- Slog.i(TAG, "service " + entry.getKey()
+ Slogf.i(TAG, "service " + entry.getKey()
+ " died; removed RadioModule with ID " + moduleId);
return;
}
@@ -128,12 +128,12 @@ public final class BroadcastRadioService {
try {
IServiceManager manager = IServiceManager.getService();
if (manager == null) {
- Slog.e(TAG, "failed to get HIDL Service Manager");
+ Slogf.e(TAG, "failed to get HIDL Service Manager");
return;
}
manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
} catch (RemoteException ex) {
- Slog.e(TAG, "failed to register for service notifications: ", ex);
+ Slogf.e(TAG, "failed to register for service notifications: ", ex);
}
}
@@ -144,12 +144,12 @@ public final class BroadcastRadioService {
try {
manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to register for service notifications: ", ex);
+ Slogf.e(TAG, "Failed to register for service notifications: ", ex);
}
}
public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
- Slog.v(TAG, "List HIDL 2.0 modules");
+ Slogf.v(TAG, "List HIDL 2.0 modules");
synchronized (mLock) {
return mModules.values().stream().map(module -> module.getProperties())
.collect(Collectors.toList());
@@ -170,7 +170,7 @@ public final class BroadcastRadioService {
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
- Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
+ Slogf.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
throw new IllegalStateException("Cannot open session for non-current user");
@@ -198,7 +198,7 @@ public final class BroadcastRadioService {
public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull IAnnouncementListener listener) {
- Slog.v(TAG, "Add announcementListener");
+ Slogf.v(TAG, "Add announcementListener");
AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
boolean anySupported = false;
synchronized (mLock) {
@@ -207,12 +207,12 @@ public final class BroadcastRadioService {
aggregator.watchModule(module, enabledTypes);
anySupported = true;
} catch (UnsupportedOperationException ex) {
- Slog.v(TAG, "Announcements not supported for this module", ex);
+ Slogf.v(TAG, "Announcements not supported for this module", ex);
}
}
}
if (!anySupported) {
- Slog.i(TAG, "There are no HAL modules that support announcements");
+ Slogf.i(TAG, "There are no HAL modules that support announcements");
}
return aggregator;
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index fb1138f4bc24..34bfa6cb2d46 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -37,21 +37,22 @@ import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
import android.os.ParcelableException;
-import android.util.Slog;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
-class Convert {
+final class Convert {
private static final String TAG = "BcRadio2Srv.convert";
@@ -111,7 +112,7 @@ class Convert {
elem.key = entry.getKey();
elem.value = entry.getValue();
if (elem.key == null || elem.value == null) {
- Slog.w(TAG, "VendorKeyValue contains null pointers");
+ Slogf.w(TAG, "VendorKeyValue contains null pointers");
continue;
}
list.add(elem);
@@ -120,20 +121,21 @@ class Convert {
return list;
}
- static @NonNull Map<String, String>
- vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
- if (info == null) return Collections.emptyMap();
+ static @NonNull Map<String, String> vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
+ Map<String, String> vendorInfoMap = new ArrayMap<>();
+ if (info == null) {
+ return vendorInfoMap;
+ }
- Map<String, String> map = new HashMap<>();
for (VendorKeyValue kvp : info) {
if (kvp.key == null || kvp.value == null) {
- Slog.w(TAG, "VendorKeyValue contains null pointers");
+ Slogf.w(TAG, "VendorKeyValue contains null pointers");
continue;
}
- map.put(kvp.key, kvp.value);
+ vendorInfoMap.put(kvp.key, kvp.value);
}
- return map;
+ return vendorInfoMap;
}
private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
@@ -168,7 +170,7 @@ class Convert {
private static @NonNull int[]
identifierTypesToProgramTypes(@NonNull int[] idTypes) {
- Set<Integer> pTypes = new HashSet<>();
+ Set<Integer> pTypes = new ArraySet<>();
for (int idType : idTypes) {
int pType = identifierTypeToProgramType(idType);
@@ -202,7 +204,7 @@ class Convert {
for (AmFmBandRange range : config.ranges) {
FrequencyBand bandType = Utils.getBand(range.lowerBound);
if (bandType == FrequencyBand.UNKNOWN) {
- Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
+ Slogf.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
continue;
}
if (bandType == FrequencyBand.FM) {
@@ -304,7 +306,7 @@ class Convert {
@NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
if (sel.primaryId.type != 0) return false;
if (sel.primaryId.value != 0) return false;
- if (sel.secondaryIds.size() != 0) return false;
+ if (!sel.secondaryIds.isEmpty()) return false;
return true;
}
@@ -319,7 +321,7 @@ class Convert {
return new ProgramSelector(
identifierTypeToProgramType(sel.primaryId.type),
Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
- secondaryIds, null);
+ secondaryIds, /* vendorIds= */ null);
}
private enum MetadataType {
@@ -335,40 +337,40 @@ class Convert {
}
}
- private static final Map<Integer, MetadataDef> metadataKeys;
+ private static final SparseArray<MetadataDef> METADATA_KEYS;
static {
- metadataKeys = new HashMap<>();
- metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef(
+ METADATA_KEYS = new SparseArray<>();
+ METADATA_KEYS.put(MetadataKey.RDS_PS, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS));
- metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RDS_PTY, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY));
- metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RBDS_PTY, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY));
- metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RDS_RT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT));
- metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_TITLE, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE));
- metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_ARTIST, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST));
- metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_ALBUM, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM));
- metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.STATION_ICON, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_ICON));
- metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.ALBUM_ART, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_ART));
- metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME));
- metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME));
- metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT));
- metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME));
- metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT));
- metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME));
- metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT));
}
@@ -376,9 +378,9 @@ class Convert {
RadioMetadata.Builder builder = new RadioMetadata.Builder();
for (Metadata entry : meta) {
- MetadataDef keyDef = metadataKeys.get(entry.key);
+ MetadataDef keyDef = METADATA_KEYS.get(entry.key);
if (keyDef == null) {
- Slog.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
+ Slogf.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
continue;
}
if (keyDef.type == MetadataType.STRING) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
index 48112c452f02..b8d12280ac05 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
@@ -19,7 +19,8 @@ package com.android.server.broadcastradio.hal2;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
final class RadioEventLogger {
private final String mTag;
@@ -30,11 +31,12 @@ final class RadioEventLogger {
mEventLogger = new LocalLog(loggerQueueSize);
}
+ @SuppressWarnings("AnnotateFormatMethod")
void logRadioEvent(String logFormat, Object... args) {
String log = String.format(logFormat, args);
mEventLogger.log(log);
if (Log.isLoggable(mTag, Log.DEBUG)) {
- Slog.d(mTag, log);
+ Slogf.d(mTag, log);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index a54af2ef6e44..0e11df8282a7 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -39,16 +39,16 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.MutableInt;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -59,12 +59,12 @@ final class RadioModule {
private static final String TAG = "BcRadio2Srv.module";
private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
- @NonNull private final IBroadcastRadio mService;
- @NonNull private final RadioManager.ModuleProperties mProperties;
+ private final IBroadcastRadio mService;
+ private final RadioManager.ModuleProperties mProperties;
private final Object mLock = new Object();
- @NonNull private final Handler mHandler;
- @NonNull private final RadioEventLogger mEventLogger;
+ private final Handler mHandler;
+ private final RadioEventLogger mEventLogger;
@GuardedBy("mLock")
private ITunerSession mHalTunerSession;
@@ -144,7 +144,7 @@ final class RadioModule {
// Collection of active AIDL tuner sessions created through openSession().
@GuardedBy("mLock")
- private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
+ private final Set<TunerSession> mAidlTunerSessions = new ArraySet<>();
@VisibleForTesting
RadioModule(@NonNull IBroadcastRadio service,
@@ -158,10 +158,10 @@ final class RadioModule {
@Nullable
static RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
try {
- Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
+ Slogf.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
IBroadcastRadio service = IBroadcastRadio.getService(fqName);
if (service == null) {
- Slog.w(TAG, "No service found for fqName " + fqName);
+ Slogf.w(TAG, "No service found for fqName " + fqName);
return null;
}
@@ -180,7 +180,7 @@ final class RadioModule {
return new RadioModule(service, prop);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to load module " + fqName, ex);
+ Slogf.e(TAG, "Failed to load module " + fqName, ex);
return null;
}
}
@@ -256,8 +256,8 @@ final class RadioModule {
}
if (idTypes == null) {
- idTypes = new HashSet<>(filter.getIdentifierTypes());
- ids = new HashSet<>(filter.getIdentifiers());
+ idTypes = new ArraySet<>(filter.getIdentifierTypes());
+ ids = new ArraySet<>(filter.getIdentifiers());
includeCategories = filter.areCategoriesIncluded();
excludeModifications = filter.areModificationsExcluded();
continue;
@@ -305,7 +305,7 @@ final class RadioModule {
try {
mHalTunerSession.stopProgramListUpdates();
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex);
}
return;
}
@@ -327,7 +327,7 @@ final class RadioModule {
newFilter));
Convert.throwOnError("startProgramListUpdates", halResult);
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);
}
}
@@ -348,7 +348,7 @@ final class RadioModule {
try {
mHalTunerSession.close();
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.close() failed: ", ex);
}
mHalTunerSession = null;
}
@@ -385,18 +385,17 @@ final class RadioModule {
runnable.run(tunerSession.mCallback);
} catch (DeadObjectException ex) {
// The other side died without calling close(), so just purge it from our records.
- Slog.e(TAG, "Removing dead TunerSession");
+ Slogf.e(TAG, "Removing dead TunerSession");
if (deadSessions == null) {
deadSessions = new ArrayList<>();
}
deadSessions.add(tunerSession);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
+ Slogf.e(TAG, "Failed to invoke ITunerCallback: ", ex);
}
}
if (deadSessions != null) {
- onTunerSessionsClosedLocked(deadSessions.toArray(
- new TunerSession[deadSessions.size()]));
+ onTunerSessionsClosedLocked(deadSessions.toArray(new TunerSession[0]));
}
}
@@ -429,7 +428,7 @@ final class RadioModule {
try {
hwCloseHandle.value.close();
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed closing announcement listener", ex);
+ Slogf.e(TAG, "Failed closing announcement listener", ex);
}
hwCloseHandle.value = null;
}
@@ -447,7 +446,9 @@ final class RadioModule {
rawImage[i] = rawList.get(i);
}
- if (rawImage == null || rawImage.length == 0) return null;
+ if (rawImage.length == 0) {
+ return null;
+ }
return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 978dc01d1219..6d435e38117f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -30,27 +30,25 @@ import android.hardware.radio.RadioManager;
import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.MutableBoolean;
import android.util.MutableInt;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-class TunerSession extends ITuner.Stub {
+final class TunerSession extends ITuner.Stub {
private static final String TAG = "BcRadio2Srv.session";
- private static final String kAudioDeviceName = "Radio tuner source";
private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
private final Object mLock = new Object();
- @NonNull private final RadioEventLogger mEventLogger;
+ private final RadioEventLogger mEventLogger;
private final RadioModule mModule;
private final ITunerSession mHwSession;
@@ -99,7 +97,7 @@ class TunerSession extends ITuner.Stub {
try {
mCallback.onError(error);
} catch (RemoteException ex) {
- Slog.w(TAG, "mCallback.onError() failed: ", ex);
+ Slogf.w(TAG, "mCallback.onError() failed: ", ex);
}
}
mModule.onTunerSessionClosed(this);
@@ -129,7 +127,7 @@ class TunerSession extends ITuner.Stub {
checkNotClosedLocked();
mDummyConfig = Objects.requireNonNull(config);
}
- Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
+ Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
}
@@ -148,7 +146,7 @@ class TunerSession extends ITuner.Stub {
if (mIsMuted == mute) return;
mIsMuted = mute;
}
- Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
+ Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
}
@Override
@@ -205,7 +203,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void cancel() {
- Slog.i(TAG, "Cancel");
+ Slogf.i(TAG, "Cancel");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG, "Cannot cancel on HAL 2.0 client from non-current user");
return;
@@ -218,7 +216,8 @@ class TunerSession extends ITuner.Stub {
@Override
public void cancelAnnouncement() {
- Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
+ Slogf.w(TAG,
+ "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
}
@Override
@@ -229,7 +228,7 @@ class TunerSession extends ITuner.Stub {
@Override
public boolean startBackgroundScan() {
- Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+ Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG,
"Cannot start background scan on HAL 2.0 client from non-current user");
@@ -240,7 +239,7 @@ class TunerSession extends ITuner.Stub {
}
@Override
- public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+ public void startProgramListUpdates(ProgramList.Filter filter) {
mEventLogger.logRadioEvent("start programList updates %s", filter);
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG,
@@ -250,8 +249,8 @@ class TunerSession extends ITuner.Stub {
// If the AIDL client provides a null filter, it wants all updates, so use the most broad
// filter.
if (filter == null) {
- filter = new ProgramList.Filter(new HashSet<Integer>(),
- new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false);
+ filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
}
synchronized (mLock) {
checkNotClosedLocked();
@@ -285,7 +284,7 @@ class TunerSession extends ITuner.Stub {
if (mProgramInfoCache == null) {
return;
}
- clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, true);
+ clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, /* purge= */ true);
}
dispatchClientUpdateChunks(clientUpdateChunks);
}
@@ -298,7 +297,7 @@ class TunerSession extends ITuner.Stub {
try {
mCallback.onProgramListUpdated(chunk);
} catch (RemoteException ex) {
- Slog.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex);
+ Slogf.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/Android.bp b/services/core/java/com/android/server/connectivity/Android.bp
new file mode 100644
index 000000000000..a374ec2cea9a
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "connectivity_flags",
+ package: "com.android.server.connectivity",
+ srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "connectivity_flags_lib",
+ aconfig_declarations: "connectivity_flags",
+}
diff --git a/services/core/java/com/android/server/connectivity/flags.aconfig b/services/core/java/com/android/server/connectivity/flags.aconfig
new file mode 100644
index 000000000000..32593d4bcdaa
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.connectivity"
+
+flag {
+ name: "replace_vpn_profile_store"
+ namespace: "android_core_networking"
+ description: "This flag controls the usage of VpnBlobStore to replace LegacyVpnProfileStore."
+ bug: "307903113"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
index ae79fc0e2c2d..43f3f0c5f3e8 100644
--- a/services/core/java/com/android/server/devicestate/OWNERS
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -1,3 +1,4 @@
ogunwale@google.com
akulian@google.com
-darryljohnson@google.com
+lihongyu@google.com
+kennethford@google.com
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 4aab9d26dbcb..06dc7f54dd8b 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -55,6 +55,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -673,14 +674,10 @@ public class AutomaticBrightnessController {
}
pw.println();
- pw.println(" mAmbientBrightnessThresholds=");
- mAmbientBrightnessThresholds.dump(pw);
- pw.println(" mScreenBrightnessThresholds=");
- mScreenBrightnessThresholds.dump(pw);
- pw.println(" mScreenBrightnessThresholdsIdle=");
- mScreenBrightnessThresholdsIdle.dump(pw);
- pw.println(" mAmbientBrightnessThresholdsIdle=");
- mAmbientBrightnessThresholdsIdle.dump(pw);
+ pw.println(" mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds);
+ pw.println(" mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle);
+ pw.println(" mScreenBrightnessThresholds=" + mScreenBrightnessThresholds);
+ pw.println(" mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle);
}
public float[] getLastSensorValues() {
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 10030b3c9176..dc0e80c686a8 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -117,6 +117,7 @@ class BrightnessRangeController {
() -> mNormalBrightnessModeController.setAutoBrightnessState(state),
() -> mHbmController.setAutoBrightnessEnabled(state)
);
+ mHdrClamper.setAutoBrightnessState(state);
}
void onBrightnessChanged(float brightness, float unthrottledBrightness,
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 09094ffb4f0d..4bbddaeb53a2 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -162,8 +162,8 @@ abstract class DisplayDevice {
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
var width = displayDeviceInfo.width;
var height = displayDeviceInfo.height;
- if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
- && displayDeviceInfo.xDpi > 0) {
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.type == Display.TYPE_EXTERNAL
+ && displayDeviceInfo.yDpi > 0 && displayDeviceInfo.xDpi > 0) {
if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
} else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 70668cbea719..85a231fafb0a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,7 +33,6 @@ import android.os.Environment;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.MathUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Spline;
@@ -46,7 +45,6 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BlockingZoneConfig;
import com.android.server.display.config.BrightnessLimitMap;
-import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
@@ -58,6 +56,7 @@ import com.android.server.display.config.EvenDimmerBrightnessData;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeout;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
@@ -80,7 +79,6 @@ import com.android.server.display.config.SdrHdrRatioPoint;
import com.android.server.display.config.SensorData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.ThresholdPoint;
import com.android.server.display.config.UsiVersion;
import com.android.server.display.config.XmlParser;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -625,13 +623,6 @@ public class DisplayDeviceConfig {
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
- private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
- private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
- private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
- private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
- private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
- private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
-
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
@@ -713,38 +704,16 @@ public class DisplayDeviceConfig {
private long mBrightnessRampIncreaseMaxIdleMillis = 0;
private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
- private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is
- private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in
- private float mScreenDarkeningMinThreshold = 0.0f; // screen brightness or ambient
- private float mScreenDarkeningMinThresholdIdle = 0.0f; // brightness.
- private float mAmbientLuxBrighteningMinThreshold = 0.0f;
- private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
- private float mAmbientLuxDarkeningMinThreshold = 0.0f;
- private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
-
- // Screen brightness thresholds levels & percentages
- private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
- private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
- // Screen brightness thresholds levels & percentages for idle mode
- private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
- private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
- // Ambient brightness thresholds levels & percentages
- private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
- private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
-
- // Ambient brightness thresholds levels & percentages for idle mode
- private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
- private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+ // Hysteresis levels for screen/ambient brightness for normal/idle modes
+ private HysteresisLevels mScreenBrightnessHysteresis =
+ HysteresisLevels.loadDisplayBrightnessConfig(null, null);
+ private HysteresisLevels mScreenBrightnessIdleHysteresis =
+ HysteresisLevels.loadDisplayBrightnessIdleConfig(null, null);
+ private HysteresisLevels mAmbientBrightnessHysteresis =
+ HysteresisLevels.loadAmbientBrightnessConfig(null, null);
+ private HysteresisLevels mAmbientBrightnessIdleHysteresis =
+ HysteresisLevels.loadAmbientBrightnessIdleConfig(null, null);
// A mapping between screen off sensor values and lux values
private int[] mScreenOffBrightnessSensorValueToLux;
@@ -1302,324 +1271,20 @@ public class DisplayDeviceConfig {
return mAmbientHorizonShort;
}
- /**
- * The minimum value for the screen brightness increase to actually occur.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenBrighteningMinThreshold() {
- return mScreenBrighteningMinThreshold;
- }
-
- /**
- * The minimum value for the screen brightness decrease to actually occur.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenDarkeningMinThreshold() {
- return mScreenDarkeningMinThreshold;
- }
-
- /**
- * The minimum value for the screen brightness increase to actually occur while in idle screen
- * brightness mode.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenBrighteningMinThresholdIdle() {
- return mScreenBrighteningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the screen brightness decrease to actually occur while in idle screen
- * brightness mode.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenDarkeningMinThresholdIdle() {
- return mScreenDarkeningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the ambient lux increase for a screen brightness change to actually
- * occur.
- * @return float value in lux.
- */
- public float getAmbientLuxBrighteningMinThreshold() {
- return mAmbientLuxBrighteningMinThreshold;
- }
-
- /**
- * The minimum value for the ambient lux decrease for a screen brightness change to actually
- * occur.
- * @return float value in lux.
- */
- public float getAmbientLuxDarkeningMinThreshold() {
- return mAmbientLuxDarkeningMinThreshold;
- }
-
- /**
- * The minimum value for the ambient lux increase for a screen brightness change to actually
- * occur while in idle screen brightness mode.
- * @return float value in lux.
- */
- public float getAmbientLuxBrighteningMinThresholdIdle() {
- return mAmbientLuxBrighteningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the ambient lux decrease for a screen brightness change to actually
- * occur while in idle screen brightness mode.
- * @return float value in lux.
- */
- public float getAmbientLuxDarkeningMinThresholdIdle() {
- return mAmbientLuxDarkeningMinThresholdIdle;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenBrighteningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n]
- * level[MAX] <= value = mScreenBrighteningPercentages[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenBrighteningPercentages applies
- */
- public float[] getScreenBrighteningLevels() {
- return mScreenBrighteningLevels;
- }
-
- /**
- * The array that describes the screen brightening threshold percentage change at each screen
- * brightness level described in mScreenBrighteningLevels.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change
- */
- public float[] getScreenBrighteningPercentages() {
- return mScreenBrighteningPercentages;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenDarkeningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n]
- * level[MAX] <= value = mScreenDarkeningPercentages[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenDarkeningPercentages applies
- */
- public float[] getScreenDarkeningLevels() {
- return mScreenDarkeningLevels;
- }
-
- /**
- * The array that describes the screen darkening threshold percentage change at each screen
- * brightness level described in mScreenDarkeningLevels.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change
- */
- public float[] getScreenDarkeningPercentages() {
- return mScreenDarkeningPercentages;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold
- * percentage applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientBrighteningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n]
- * level[MAX] <= value = mAmbientBrighteningPercentages[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientBrighteningPercentages applies
- */
- public float[] getAmbientBrighteningLevels() {
- return mAmbientBrighteningLevels;
- }
-
- /**
- * The array that describes the ambient brightening threshold percentage change at each ambient
- * brightness level described in mAmbientBrighteningLevels.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change
- */
- public float[] getAmbientBrighteningPercentages() {
- return mAmbientBrighteningPercentages;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientDarkeningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n]
- * level[MAX] <= value = mAmbientDarkeningPercentages[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientDarkeningPercentages applies
- */
- public float[] getAmbientDarkeningLevels() {
- return mAmbientDarkeningLevels;
- }
-
- /**
- * The array that describes the ambient darkening threshold percentage change at each ambient
- * brightness level described in mAmbientDarkeningLevels.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change
- */
- public float[] getAmbientDarkeningPercentages() {
- return mAmbientDarkeningPercentages;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenBrighteningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n]
- * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenBrighteningPercentagesIdle applies
- */
- public float[] getScreenBrighteningLevelsIdle() {
- return mScreenBrighteningLevelsIdle;
- }
-
- /**
- * The array that describes the screen brightening threshold percentage change at each screen
- * brightness level described in mScreenBrighteningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change while in idle mode.
- */
- public float[] getScreenBrighteningPercentagesIdle() {
- return mScreenBrighteningPercentagesIdle;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenDarkeningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n]
- * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenDarkeningPercentagesIdle applies
- */
- public float[] getScreenDarkeningLevelsIdle() {
- return mScreenDarkeningLevelsIdle;
+ public HysteresisLevels getAmbientBrightnessHysteresis() {
+ return mAmbientBrightnessHysteresis;
}
- /**
- * The array that describes the screen darkening threshold percentage change at each screen
- * brightness level described in mScreenDarkeningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change while in idle mode.
- */
- public float[] getScreenDarkeningPercentagesIdle() {
- return mScreenDarkeningPercentagesIdle;
+ public HysteresisLevels getAmbientBrightnessIdleHysteresis() {
+ return mAmbientBrightnessIdleHysteresis;
}
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientBrighteningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n]
- * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientBrighteningPercentagesIdle applies
- */
- public float[] getAmbientBrighteningLevelsIdle() {
- return mAmbientBrighteningLevelsIdle;
+ public HysteresisLevels getScreenBrightnessHysteresis() {
+ return mScreenBrightnessHysteresis;
}
- /**
- * The array that describes the ambient brightness threshold percentage change whilst in
- * idle screen brightness mode at each ambient brightness level described in
- * mAmbientBrighteningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of ambient brightness increase required in order
- * for the screen brightness to change
- */
- public float[] getAmbientBrighteningPercentagesIdle() {
- return mAmbientBrighteningPercentagesIdle;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientDarkeningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n]
- * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientDarkeningPercentagesIdle applies
- */
- public float[] getAmbientDarkeningLevelsIdle() {
- return mAmbientDarkeningLevelsIdle;
- }
-
- /**
- * The array that describes the ambient brightness threshold percentage change whilst in
- * idle screen brightness mode at each ambient brightness level described in
- * mAmbientDarkeningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of ambient brightness decrease required in order
- * for the screen brightness to change
- */
- public float[] getAmbientDarkeningPercentagesIdle() {
- return mAmbientDarkeningPercentagesIdle;
+ public HysteresisLevels getScreenBrightnessIdleHysteresis() {
+ return mScreenBrightnessIdleHysteresis;
}
public SensorData getAmbientLightSensor() {
@@ -1985,49 +1650,13 @@ public class DisplayDeviceConfig {
+ "mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ "\n"
- + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
- + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
- + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
- + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
- + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
- + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle
- + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
- + ", mAmbientLuxBrighteningMinThresholdIdle="
- + mAmbientLuxBrighteningMinThresholdIdle
+ + "mAmbientBrightnessHysteresis=" + mAmbientBrightnessHysteresis
+ + "\n"
+ + "mAmbientIdleHysteresis=" + mAmbientBrightnessIdleHysteresis
+ "\n"
- + "mScreenBrighteningLevels=" + Arrays.toString(
- mScreenBrighteningLevels)
- + ", mScreenBrighteningPercentages=" + Arrays.toString(
- mScreenBrighteningPercentages)
- + ", mScreenDarkeningLevels=" + Arrays.toString(
- mScreenDarkeningLevels)
- + ", mScreenDarkeningPercentages=" + Arrays.toString(
- mScreenDarkeningPercentages)
- + ", mAmbientBrighteningLevels=" + Arrays.toString(
- mAmbientBrighteningLevels)
- + ", mAmbientBrighteningPercentages=" + Arrays.toString(
- mAmbientBrighteningPercentages)
- + ", mAmbientDarkeningLevels=" + Arrays.toString(
- mAmbientDarkeningLevels)
- + ", mAmbientDarkeningPercentages=" + Arrays.toString(
- mAmbientDarkeningPercentages)
+ + "mScreenBrightnessHysteresis=" + mScreenBrightnessHysteresis
+ "\n"
- + "mAmbientBrighteningLevelsIdle=" + Arrays.toString(
- mAmbientBrighteningLevelsIdle)
- + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
- mAmbientBrighteningPercentagesIdle)
- + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
- mAmbientDarkeningLevelsIdle)
- + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
- mAmbientDarkeningPercentagesIdle)
- + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
- mScreenBrighteningLevelsIdle)
- + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
- mScreenBrighteningPercentagesIdle)
- + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
- mScreenDarkeningLevelsIdle)
- + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
- mScreenDarkeningPercentagesIdle)
+ + "mScreenBrightnessIdleHysteresis=" + mScreenBrightnessIdleHysteresis
+ "\n"
+ "mAmbientLightSensor=" + mAmbientLightSensor
+ ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
@@ -3137,281 +2766,15 @@ public class DisplayDeviceConfig {
}
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
- loadDisplayBrightnessThresholds(config);
- loadAmbientBrightnessThresholds(config);
- loadDisplayBrightnessThresholdsIdle(config);
- loadAmbientBrightnessThresholdsIdle(config);
- }
-
- private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
- BrightnessThresholds brighteningScreen = null;
- BrightnessThresholds darkeningScreen = null;
- if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
- brighteningScreen =
- config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
- darkeningScreen =
- config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
-
- }
-
- // Screen bright/darkening threshold levels for active mode
- Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningScreen,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenBrighteningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
-
- mScreenBrighteningLevels = screenBrighteningPair.first;
- mScreenBrighteningPercentages = screenBrighteningPair.second;
-
- Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningScreen,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenDarkeningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenDarkeningLevels = screenDarkeningPair.first;
- mScreenDarkeningPercentages = screenDarkeningPair.second;
-
- // Screen bright/darkening threshold minimums for active mode
- if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
- mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
- }
- if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
- mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
- }
- }
-
- private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
- // Ambient Brightness Threshold Levels
- BrightnessThresholds brighteningAmbientLux = null;
- BrightnessThresholds darkeningAmbientLux = null;
- if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
- brighteningAmbientLux =
- config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
- darkeningAmbientLux =
- config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
- }
-
- // Ambient bright/darkening threshold levels for active mode
- Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningAmbientLux,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientBrighteningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
- mAmbientBrighteningLevels = ambientBrighteningPair.first;
- mAmbientBrighteningPercentages = ambientBrighteningPair.second;
-
- Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningAmbientLux,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientDarkeningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
- mAmbientDarkeningLevels = ambientDarkeningPair.first;
- mAmbientDarkeningPercentages = ambientDarkeningPair.second;
-
- // Ambient bright/darkening threshold minimums for active/idle mode
- if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
- mAmbientLuxBrighteningMinThreshold =
- brighteningAmbientLux.getMinimum().floatValue();
- }
-
- if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
- mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
- }
- }
-
- private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
- BrightnessThresholds brighteningScreenIdle = null;
- BrightnessThresholds darkeningScreenIdle = null;
- if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
- brighteningScreenIdle =
- config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
- darkeningScreenIdle =
- config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
- }
-
- Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningScreenIdle,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenBrighteningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
- mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
-
- Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningScreenIdle,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenDarkeningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
- mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
-
- if (brighteningScreenIdle != null
- && brighteningScreenIdle.getMinimum() != null) {
- mScreenBrighteningMinThresholdIdle =
- brighteningScreenIdle.getMinimum().floatValue();
- }
- if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
- mScreenDarkeningMinThresholdIdle =
- darkeningScreenIdle.getMinimum().floatValue();
- }
- }
-
- private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
- BrightnessThresholds brighteningAmbientLuxIdle = null;
- BrightnessThresholds darkeningAmbientLuxIdle = null;
- if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
- brighteningAmbientLuxIdle =
- config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
- darkeningAmbientLuxIdle =
- config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
- }
-
- Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningAmbientLuxIdle,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientBrighteningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
- mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
- mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
-
- Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningAmbientLuxIdle,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientDarkeningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
- mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
- mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
-
- if (brighteningAmbientLuxIdle != null
- && brighteningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxBrighteningMinThresholdIdle =
- brighteningAmbientLuxIdle.getMinimum().floatValue();
- }
-
- if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxDarkeningMinThresholdIdle =
- darkeningAmbientLuxIdle.getMinimum().floatValue();
- }
- }
-
- private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
- int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
- float[] defaultPercentage) {
- return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
- configFallbackPercentage, defaultLevels, defaultPercentage, false);
- }
-
- // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
- // percentages for brightness levels at or above the lux value.
- // Historically, config.xml would have an array for brightness levels that was 1 shorter than
- // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
- // rest of the framework. Values were also defined in different units (permille vs percent).
- private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
- int configFallbackThreshold, int configFallbackPermille,
- float[] defaultLevels, float[] defaultPercentage,
- boolean potentialOldBrightnessScale) {
- if (thresholds != null
- && thresholds.getBrightnessThresholdPoints() != null
- && thresholds.getBrightnessThresholdPoints()
- .getBrightnessThresholdPoint().size() != 0) {
-
- // The level and percentages arrays are equal length in the ddc (new system)
- List<ThresholdPoint> points =
- thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
- final int size = points.size();
-
- float[] thresholdLevels = new float[size];
- float[] thresholdPercentages = new float[size];
-
- int i = 0;
- for (ThresholdPoint point : points) {
- thresholdLevels[i] = point.getThreshold().floatValue();
- thresholdPercentages[i] = point.getPercentage().floatValue();
- i++;
- }
- return new Pair<>(thresholdLevels, thresholdPercentages);
- } else {
- // The level and percentages arrays are unequal length in config.xml (old system)
- // We prefix the array with a 0 value to ensure they can be handled consistently
- // with the new system.
-
- // Load levels array
- int[] configThresholdArray = mContext.getResources().getIntArray(
- configFallbackThreshold);
- int configThresholdsSize;
- if (configThresholdArray == null || configThresholdArray.length == 0) {
- configThresholdsSize = 1;
- } else {
- configThresholdsSize = configThresholdArray.length + 1;
- }
-
-
- // Load percentage array
- int[] configPermille = mContext.getResources().getIntArray(
- configFallbackPermille);
-
- // Ensure lengths match up
- boolean emptyArray = configPermille == null || configPermille.length == 0;
- if (emptyArray && configThresholdsSize == 1) {
- return new Pair<>(defaultLevels, defaultPercentage);
- }
- if (emptyArray || configPermille.length != configThresholdsSize) {
- throw new IllegalArgumentException(
- "Brightness threshold arrays do not align in length");
- }
-
- // Calculate levels array
- float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
- // Start at 1, so that 0 index value is 0.0f (default)
- for (int i = 1; i < configThresholdsSize; i++) {
- configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
- }
- if (potentialOldBrightnessScale) {
- configThresholdWithZeroPrefixed =
- constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
- }
-
- // Calculate percentages array
- float[] configPercentage = new float[configThresholdsSize];
- for (int i = 0; i < configPermille.length; i++) {
- configPercentage[i] = configPermille[i] / 10.0f;
- } return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
- }
- }
-
- /**
- * This check is due to historical reasons, where screen thresholdLevels used to be
- * integer values in the range of [0-255], but then was changed to be float values from [0,1].
- * To accommodate both the possibilities, we first check if all the thresholdLevels are in
- * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
- */
- private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
- if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
- /* maxValueInclusive= */ 1.0f)) {
- return thresholdLevels;
- }
-
- Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
- float[] thresholdLevelsScaled = new float[thresholdLevels.length];
- for (int index = 0; thresholdLevels.length > index; ++index) {
- thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
- }
- return thresholdLevelsScaled;
- }
-
- private boolean isAllInRange(float[] configArray, float minValueInclusive,
- float maxValueInclusive) {
- for (float v : configArray) {
- if (v < minValueInclusive || v > maxValueInclusive) {
- return false;
- }
- }
- return true;
+ Resources res = mContext.getResources();
+ mScreenBrightnessHysteresis =
+ HysteresisLevels.loadDisplayBrightnessConfig(config, res);
+ mScreenBrightnessIdleHysteresis =
+ HysteresisLevels.loadDisplayBrightnessIdleConfig(config, res);
+ mAmbientBrightnessHysteresis =
+ HysteresisLevels.loadAmbientBrightnessConfig(config, res);
+ mAmbientBrightnessIdleHysteresis =
+ HysteresisLevels.loadAmbientBrightnessIdleConfig(config, res);
}
private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 40f0362ff8f3..31092f27c838 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -757,6 +757,7 @@ public final class DisplayManagerService extends SystemService {
mContext.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mHandler), new DeviceStateListener());
+ mLogicalDisplayMapper.onWindowManagerReady();
scheduleTraversalLocked(false);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b21a89a92803..404c3ad3a51b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -86,6 +86,7 @@ import com.android.server.display.brightness.clamper.BrightnessClamperController
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.state.DisplayStateController;
@@ -1050,76 +1051,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (defaultModeBrightnessMapper != null) {
// Ambient Lux - Active Mode Brightness Thresholds
- float[] ambientBrighteningThresholds =
- mDisplayDeviceConfig.getAmbientBrighteningPercentages();
- float[] ambientDarkeningThresholds =
- mDisplayDeviceConfig.getAmbientDarkeningPercentages();
- float[] ambientBrighteningLevels =
- mDisplayDeviceConfig.getAmbientBrighteningLevels();
- float[] ambientDarkeningLevels =
- mDisplayDeviceConfig.getAmbientDarkeningLevels();
- float ambientDarkeningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
- float ambientBrighteningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
- ambientBrighteningMinThreshold);
+ HysteresisLevels ambientBrightnessThresholds =
+ mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
// Display - Active Mode Brightness Thresholds
- float[] screenBrighteningThresholds =
- mDisplayDeviceConfig.getScreenBrighteningPercentages();
- float[] screenDarkeningThresholds =
- mDisplayDeviceConfig.getScreenDarkeningPercentages();
- float[] screenBrighteningLevels =
- mDisplayDeviceConfig.getScreenBrighteningLevels();
- float[] screenDarkeningLevels =
- mDisplayDeviceConfig.getScreenDarkeningLevels();
- float screenDarkeningMinThreshold =
- mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
- float screenBrighteningMinThreshold =
- mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds,
- screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
- screenBrighteningMinThreshold, true);
+ HysteresisLevels screenBrightnessThresholds =
+ mDisplayDeviceConfig.getScreenBrightnessHysteresis();
// Ambient Lux - Idle Screen Brightness Thresholds
- float ambientDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
- float ambientBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
- float[] ambientBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
- float[] ambientDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
- float[] ambientBrighteningLevelsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
- float[] ambientDarkeningLevelsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
- ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
- ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+ HysteresisLevels ambientBrightnessThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
// Display - Idle Screen Brightness Thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- float[] screenBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
- float[] screenDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
- float[] screenBrighteningLevelsIdle =
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
- float[] screenDarkeningLevelsIdle =
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
- screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+ HysteresisLevels screenBrightnessThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
@@ -3208,25 +3153,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
- }
-
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
- potentialOldBrightnessRange);
- }
-
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager,
Sensor lightSensor,
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
deleted file mode 100644
index 0521b8ac4f3b..000000000000
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import android.util.Slog;
-
-import com.android.server.display.utils.DebugUtils;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-/**
- * A helper class for handling access to illuminance hysteresis level values.
- */
-public class HysteresisLevels {
- private static final String TAG = "HysteresisLevels";
-
- // To enable these logs, run:
- // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
- private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
- private final float[] mBrighteningThresholdsPercentages;
- private final float[] mDarkeningThresholdsPercentages;
- private final float[] mBrighteningThresholdLevels;
- private final float[] mDarkeningThresholdLevels;
- private final float mMinDarkening;
- private final float mMinBrightening;
-
- /**
- * Creates a {@code HysteresisLevels} object with the given equal-length
- * float arrays.
- * @param brighteningThresholdsPercentages 0-100 of thresholds
- * @param darkeningThresholdsPercentages 0-100 of thresholds
- * @param brighteningThresholdLevels float array of brightness values in the relevant units
- * @param minBrighteningThreshold the minimum value for which the brightening value needs to
- * return.
- * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
- * @param potentialOldBrightnessRange whether or not the values used could be from the old
- * screen brightness range ie, between 1-255.
- */
- HysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages,
- float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
- float minDarkeningThreshold, float minBrighteningThreshold,
- boolean potentialOldBrightnessRange) {
- if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
- || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
- throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
- }
- mBrighteningThresholdsPercentages =
- setArrayFormat(brighteningThresholdsPercentages, 100.0f);
- mDarkeningThresholdsPercentages =
- setArrayFormat(darkeningThresholdsPercentages, 100.0f);
- mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
- mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
- mMinDarkening = minDarkeningThreshold;
- mMinBrightening = minBrighteningThreshold;
- }
-
- HysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages,
- float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
- float minDarkeningThreshold, float minBrighteningThreshold) {
- this(brighteningThresholdsPercentages, darkeningThresholdsPercentages,
- brighteningThresholdLevels, darkeningThresholdLevels, minDarkeningThreshold,
- minBrighteningThreshold, false);
- }
-
- /**
- * Return the brightening hysteresis threshold for the given value level.
- */
- public float getBrighteningThreshold(float value) {
- final float brightConstant = getReferenceLevel(value,
- mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
-
- float brightThreshold = value * (1.0f + brightConstant);
- if (DEBUG) {
- Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
- + brightThreshold + ", value=" + value);
- }
-
- brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
- return brightThreshold;
- }
-
- /**
- * Return the darkening hysteresis threshold for the given value level.
- */
- public float getDarkeningThreshold(float value) {
- final float darkConstant = getReferenceLevel(value,
- mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
- float darkThreshold = value * (1.0f - darkConstant);
- if (DEBUG) {
- Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
- + darkThreshold + ", value=" + value);
- }
- darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
- return Math.max(darkThreshold, 0.0f);
- }
-
- /**
- * Return the hysteresis constant for the closest threshold value from the given array.
- */
- private float getReferenceLevel(float value, float[] thresholdLevels,
- float[] thresholdPercentages) {
- if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
- return 0.0f;
- }
- int index = 0;
- while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
- index++;
- }
- return thresholdPercentages[index];
- }
-
- /**
- * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
- */
- private float[] setArrayFormat(float[] configArray, float divideFactor) {
- float[] levelArray = new float[configArray.length];
- for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = configArray[index] / divideFactor;
- }
- return levelArray;
- }
-
- void dump(PrintWriter pw) {
- pw.println("HysteresisLevels");
- pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
- pw.println(" mBrighteningThresholdsPercentages="
- + Arrays.toString(mBrighteningThresholdsPercentages));
- pw.println(" mMinBrightening=" + mMinBrightening);
- pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
- pw.println(" mDarkeningThresholdsPercentages="
- + Arrays.toString(mDarkeningThresholdsPercentages));
- pw.println(" mMinDarkening=" + mMinDarkening);
- }
-}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 22e4bc502131..189e3669de5b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -482,7 +482,8 @@ final class LogicalDisplay {
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
- if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+ if (mIsAnisotropyCorrectionEnabled && deviceInfo.type == Display.TYPE_EXTERNAL
+ && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
} else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
@@ -711,8 +712,8 @@ final class LogicalDisplay {
var displayLogicalWidth = displayInfo.logicalWidth;
var displayLogicalHeight = displayInfo.logicalHeight;
- if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
- && displayDeviceInfo.yDpi > 0) {
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.type == Display.TYPE_EXTERNAL
+ && displayDeviceInfo.xDpi > 0 && displayDeviceInfo.yDpi > 0) {
if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
if (rotated) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 6203a32151a0..bca53cf02f69 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -41,10 +41,12 @@ import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.foldables.FoldGracePeriodProvider;
+import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import com.android.server.display.utils.DebugUtils;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.FoldSettingProvider;
import java.io.PrintWriter;
@@ -189,6 +191,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
* #updateLogicalDisplaysLocked} to establish which Virtual Devices own which Virtual Displays.
*/
private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
+ private WindowManagerPolicy mWindowManagerPolicy;
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
private final DisplayIdProducer mIdProducer = (isDefault) ->
@@ -274,6 +277,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mListener.onTraversalRequested();
}
+ public void onWindowManagerReady() {
+ mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
+ }
+
public LogicalDisplay getDisplayLocked(int displayId) {
return getDisplayLocked(displayId, /* includeDisabled= */ true);
}
@@ -1114,14 +1121,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
+ boolean newDisplayCreated = false;
if (newDisplay == null) {
newDisplay = createNewLogicalDisplayLocked(
null /*displayDevice*/, logicalDisplayId);
+ newDisplayCreated = true;
}
// Now swap the underlying display devices between the old display and the new display
final LogicalDisplay oldDisplay = getDisplayLocked(device);
if (newDisplay != oldDisplay) {
+ // Display is swapping, notify WindowManager, so it can prepare for
+ // the display switch
+ if (!newDisplayCreated && mWindowManagerPolicy != null) {
+ mWindowManagerPolicy.onDisplaySwitchStart(newDisplay.getDisplayIdLocked());
+ }
+
newDisplay.swapDisplaysLocked(oldDisplay);
}
DisplayDeviceConfig config = device.getDisplayDeviceConfig();
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index 135ebd8f4fbf..e94cf00437eb 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -79,10 +79,12 @@ class NormalBrightnessModeController {
maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
}
- if (maxBrightnessPoints == null) {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // Temporary disabling this Controller if auto brightness is off, to avoid capping
+ // brightness based on stale ambient lux. The issue is tracked here: b/322445088
+ if (mAutoBrightnessEnabled && maxBrightnessPoints == null) {
maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
}
-
if (maxBrightnessPoints != null) {
for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
float ambientBoundary = brightnessPoint.getKey();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 01a8d360a526..f1cb66c0efbb 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,6 +24,7 @@ import android.os.PowerManager;
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -56,6 +57,8 @@ public class HdrClamper {
private float mTransitionRate = -1f;
private float mDesiredTransitionRate = -1f;
+ private boolean mAutoBrightnessEnabled = false;
+
public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
Handler handler) {
this(clamperChangeListener, handler, new Injector());
@@ -122,6 +125,18 @@ public class HdrClamper {
recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
}
+ /**
+ * Sets state of auto brightness to temporary disabling this Clamper if auto brightness is off.
+ * The issue is tracked here: b/322445088
+ */
+ public void setAutoBrightnessState(int state) {
+ boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ if (isEnabled != mAutoBrightnessEnabled) {
+ mAutoBrightnessEnabled = isEnabled;
+ recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
+ }
+ }
+
/** Clean up all resources */
@SuppressLint("AndroidFrameworkRequiresPermission")
public void stop() {
@@ -145,6 +160,7 @@ public class HdrClamper {
: mHdrBrightnessData.toString()));
pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
}
private void reset() {
@@ -163,7 +179,10 @@ public class HdrClamper {
private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
boolean hdrVisible) {
- if (data == null || !hdrVisible) {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // Temporary disabling this Clamper if auto brightness is off, to avoid capping
+ // brightness based on stale ambient lux. The issue is tracked here: b/322445088
+ if (data == null || !hdrVisible || !mAutoBrightnessEnabled) {
reset();
return;
}
diff --git a/services/core/java/com/android/server/display/config/HysteresisLevels.java b/services/core/java/com/android/server/display/config/HysteresisLevels.java
new file mode 100644
index 000000000000..e659d88c9752
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HysteresisLevels.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.ArrayRes;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.DebugUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A helper class for handling access to illuminance hysteresis level values.
+ */
+public class HysteresisLevels {
+ private static final String TAG = "HysteresisLevels";
+
+ private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+ private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+ /**
+ * The array that describes the brightness threshold percentage change
+ * at each brightness level described in mBrighteningThresholdLevels.
+ */
+ private final float[] mBrighteningThresholdsPercentages;
+
+ /**
+ * The array that describes the brightness threshold percentage change
+ * at each brightness level described in mDarkeningThresholdLevels.
+ */
+ private final float[] mDarkeningThresholdsPercentages;
+
+ /**
+ * The array that describes the range of brightness that each threshold percentage applies to
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current brightness value
+ * level = mBrighteningThresholdLevels
+ *
+ * condition return
+ * value < mBrighteningThresholdLevels[0] = 0.0f
+ * level[n] <= value < level[n+1] = mBrighteningThresholdsPercentages[n]
+ * level[MAX] <= value = mBrighteningThresholdsPercentages[MAX]
+ */
+ private final float[] mBrighteningThresholdLevels;
+
+ /**
+ * The array that describes the range of brightness that each threshold percentage applies to
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current brightness value
+ * level = mDarkeningThresholdLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mDarkeningThresholdsPercentages[n]
+ * level[MAX] <= value = mDarkeningThresholdsPercentages[MAX]
+ */
+ private final float[] mDarkeningThresholdLevels;
+
+ /**
+ * The minimum value decrease for darkening event
+ */
+ private final float mMinDarkening;
+
+ /**
+ * The minimum value increase for brightening event.
+ */
+ private final float mMinBrightening;
+
+ /**
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * float arrays.
+ *
+ * @param brighteningThresholdsPercentages 0-100 of thresholds
+ * @param darkeningThresholdsPercentages 0-100 of thresholds
+ * @param brighteningThresholdLevels float array of brightness values in the relevant
+ * units
+ * @param minBrighteningThreshold the minimum value for which the brightening value
+ * needs to
+ * return.
+ * @param minDarkeningThreshold the minimum value for which the darkening value needs
+ * to return.
+ */
+ @VisibleForTesting
+ public HysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages,
+ float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+ float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+ || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
+ throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+ }
+ mBrighteningThresholdsPercentages =
+ setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+ mDarkeningThresholdsPercentages =
+ setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+ mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+ mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
+ mMinDarkening = minDarkeningThreshold;
+ mMinBrightening = minBrighteningThreshold;
+ }
+
+ /**
+ * Return the brightening hysteresis threshold for the given value level.
+ */
+ public float getBrighteningThreshold(float value) {
+ final float brightConstant = getReferenceLevel(value,
+ mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
+ float brightThreshold = value * (1.0f + brightConstant);
+ if (DEBUG) {
+ Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+ + brightThreshold + ", value=" + value);
+ }
+
+ brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
+ return brightThreshold;
+ }
+
+ /**
+ * Return the darkening hysteresis threshold for the given value level.
+ */
+ public float getDarkeningThreshold(float value) {
+ final float darkConstant = getReferenceLevel(value,
+ mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
+ float darkThreshold = value * (1.0f - darkConstant);
+ if (DEBUG) {
+ Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+ + darkThreshold + ", value=" + value);
+ }
+ darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+ return Math.max(darkThreshold, 0.0f);
+ }
+
+ @VisibleForTesting
+ public float[] getBrighteningThresholdsPercentages() {
+ return mBrighteningThresholdsPercentages;
+ }
+
+ @VisibleForTesting
+ public float[] getDarkeningThresholdsPercentages() {
+ return mDarkeningThresholdsPercentages;
+ }
+
+ @VisibleForTesting
+ public float[] getBrighteningThresholdLevels() {
+ return mBrighteningThresholdLevels;
+ }
+
+ @VisibleForTesting
+ public float[] getDarkeningThresholdLevels() {
+ return mDarkeningThresholdLevels;
+ }
+
+ @VisibleForTesting
+ public float getMinDarkening() {
+ return mMinDarkening;
+ }
+
+ @VisibleForTesting
+ public float getMinBrightening() {
+ return mMinBrightening;
+ }
+
+ /**
+ * Return the hysteresis constant for the closest threshold value from the given array.
+ */
+ private float getReferenceLevel(float value, float[] thresholdLevels,
+ float[] thresholdPercentages) {
+ if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+ return 0.0f;
+ }
+ int index = 0;
+ while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+ index++;
+ }
+ return thresholdPercentages[index];
+ }
+
+ /**
+ * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
+ */
+ private float[] setArrayFormat(float[] configArray, float divideFactor) {
+ float[] levelArray = new float[configArray.length];
+ for (int index = 0; levelArray.length > index; ++index) {
+ levelArray[index] = configArray[index] / divideFactor;
+ }
+ return levelArray;
+ }
+
+ @Override
+ public String toString() {
+ return "HysteresisLevels {"
+ + "\n"
+ + " mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)
+ + ",\n"
+ + " mBrighteningThresholdsPercentages="
+ + Arrays.toString(mBrighteningThresholdsPercentages)
+ + ",\n"
+ + " mMinBrightening=" + mMinBrightening
+ + ",\n"
+ + " mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels)
+ + ",\n"
+ + " mDarkeningThresholdsPercentages="
+ + Arrays.toString(mDarkeningThresholdsPercentages)
+ + ",\n"
+ + " mMinDarkening=" + mMinDarkening
+ + "\n"
+ + "}";
+ }
+
+ /**
+ * Creates hysteresis levels for Active Ambient Lux
+ */
+ public static HysteresisLevels loadAmbientBrightnessConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getAmbientBrightnessChangeThresholds(),
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+ DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+ DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ false);
+ }
+
+ /**
+ * Creates hysteresis levels for Active Screen Brightness
+ */
+ public static HysteresisLevels loadDisplayBrightnessConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getDisplayBrightnessChangeThresholds(),
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS,
+ DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ true);
+ }
+
+ /**
+ * Creates hysteresis levels for Idle Ambient Lux
+ */
+ public static HysteresisLevels loadAmbientBrightnessIdleConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getAmbientBrightnessChangeThresholdsIdle(),
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+ DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+ DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ false);
+ }
+
+ /**
+ * Creates hysteresis levels for Idle Screen Brightness
+ */
+ public static HysteresisLevels loadDisplayBrightnessIdleConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getDisplayBrightnessChangeThresholdsIdle(),
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS,
+ DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ true);
+ }
+
+
+ private static HysteresisLevels createHysteresisLevels(
+ @Nullable Thresholds thresholds,
+ @ArrayRes int configLevels,
+ @ArrayRes int configBrighteningThresholds,
+ @ArrayRes int configDarkeningThresholds,
+ float[] defaultLevels,
+ float[] defaultBrighteningThresholds,
+ float[] defaultDarkeningThresholds,
+ @Nullable Resources resources,
+ boolean potentialOldBrightnessScale
+ ) {
+ BrightnessThresholds brighteningThresholds =
+ thresholds == null ? null : thresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningThresholds =
+ thresholds == null ? null : thresholds.getDarkeningThresholds();
+
+ Pair<float[], float[]> brighteningPair = getBrightnessLevelAndPercentage(
+ brighteningThresholds,
+ configLevels, configBrighteningThresholds,
+ defaultLevels, defaultBrighteningThresholds,
+ potentialOldBrightnessScale, resources);
+
+ Pair<float[], float[]> darkeningPair = getBrightnessLevelAndPercentage(
+ darkeningThresholds,
+ configLevels, configDarkeningThresholds,
+ defaultLevels, defaultDarkeningThresholds,
+ potentialOldBrightnessScale, resources);
+
+ float brighteningMinThreshold =
+ brighteningThresholds != null && brighteningThresholds.getMinimum() != null
+ ? brighteningThresholds.getMinimum().floatValue() : 0f;
+ float darkeningMinThreshold =
+ darkeningThresholds != null && darkeningThresholds.getMinimum() != null
+ ? darkeningThresholds.getMinimum().floatValue() : 0f;
+
+ return new HysteresisLevels(
+ brighteningPair.second,
+ darkeningPair.second,
+ brighteningPair.first,
+ darkeningPair.first,
+ darkeningMinThreshold,
+ brighteningMinThreshold
+ );
+ }
+
+ // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+ // percentages for brightness levels at or above the lux value.
+ // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+ // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+ // rest of the framework. Values were also defined in different units (permille vs percent).
+ private static Pair<float[], float[]> getBrightnessLevelAndPercentage(
+ @Nullable BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPermille,
+ float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale,
+ @Nullable Resources resources) {
+ if (thresholds != null
+ && thresholds.getBrightnessThresholdPoints() != null
+ && !thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint()
+ .isEmpty()) {
+
+ // The level and percentages arrays are equal length in the ddc (new system)
+ List<ThresholdPoint> points =
+ thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+ final int size = points.size();
+
+ float[] thresholdLevels = new float[size];
+ float[] thresholdPercentages = new float[size];
+
+ int i = 0;
+ for (ThresholdPoint point : points) {
+ thresholdLevels[i] = point.getThreshold().floatValue();
+ thresholdPercentages[i] = point.getPercentage().floatValue();
+ i++;
+ }
+ return new Pair<>(thresholdLevels, thresholdPercentages);
+ } else if (resources != null) {
+ // The level and percentages arrays are unequal length in config.xml (old system)
+ // We prefix the array with a 0 value to ensure they can be handled consistently
+ // with the new system.
+
+ // Load levels array
+ int[] configThresholdArray = resources.getIntArray(configFallbackThreshold);
+ int configThresholdsSize;
+ // null check is not needed here, however it test we are mocking resources that might
+ // return null
+ if (configThresholdArray == null || configThresholdArray.length == 0) {
+ configThresholdsSize = 1;
+ } else {
+ configThresholdsSize = configThresholdArray.length + 1;
+ }
+
+ // Load percentage array
+ int[] configPermille = resources.getIntArray(configFallbackPermille);
+
+ // Ensure lengths match up
+ // null check is not needed here, however it test we are mocking resources that might
+ // return null
+ boolean emptyArray = configPermille == null || configPermille.length == 0;
+ if (emptyArray && configThresholdsSize == 1) {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ if (emptyArray || configPermille.length != configThresholdsSize) {
+ throw new IllegalArgumentException(
+ "Brightness threshold arrays do not align in length");
+ }
+
+ // Calculate levels array
+ float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+ // Start at 1, so that 0 index value is 0.0f (default)
+ for (int i = 1; i < configThresholdsSize; i++) {
+ configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+ }
+ if (potentialOldBrightnessScale) {
+ configThresholdWithZeroPrefixed =
+ constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+ }
+
+ // Calculate percentages array
+ float[] configPercentage = new float[configThresholdsSize];
+ for (int i = 0; i < configPermille.length; i++) {
+ configPercentage[i] = configPermille[i] / 10.0f;
+ }
+ return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+ } else {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ }
+
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+ * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private static float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+ /* maxValueInclusive= */ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private static boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ for (float v : configArray) {
+ if (v < minValueInclusive || v > maxValueInclusive) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/services/core/java/com/android/server/display/feature/Android.bp b/services/core/java/com/android/server/display/feature/Android.bp
index a0ead384c1d2..daf8832fd1d7 100644
--- a/services/core/java/com/android/server/display/feature/Android.bp
+++ b/services/core/java/com/android/server/display/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "display_flags",
package: "com.android.server.display.feature.flags",
+ container: "system",
srcs: [
"*.aconfig",
],
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index d4319cc2b633..c68ef9bd4cce 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.display.feature.flags"
+container: "system"
# Important: Flags must be accessed through DisplayManagerFlags.
diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp
index 067288d6650d..b0fbab6657b0 100644
--- a/services/core/java/com/android/server/feature/Android.bp
+++ b/services/core/java/com/android/server/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "dropbox_flags",
package: "com.android.server.feature.flags",
+ container: "system",
srcs: [
"dropbox_flags.aconfig",
],
diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
index 14e964b26c6b..98978f03fae5 100644
--- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig
+++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.feature.flags"
+container: "system"
flag{
name: "enable_read_dropbox_permission"
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7b844a099841..f3836794c32e 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -165,7 +165,11 @@ public final class FontManagerService extends IFontManager.Stub {
@Override
public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ final int latestFontLoadBootPhase =
+ (Flags.completeFontLoadInSystemServicesReady())
+ ? SystemService.PHASE_SYSTEM_SERVICES_READY
+ : SystemService.PHASE_ACTIVITY_MANAGER_READY;
+ if (phase == latestFontLoadBootPhase) {
// Wait for FontManagerService to start since it will be needed after this point.
mServiceStarted.join();
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 9d04682f2374..ea240c75452d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,7 +196,12 @@ final class UpdatableFontDir {
File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
if (!signatureFile.exists()) {
Slog.i(TAG, "The signature file is missing.");
- return;
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ return;
+ } else {
+ FileUtils.deleteContentsAndDir(dir);
+ continue;
+ }
}
byte[] signature;
try {
@@ -221,33 +226,39 @@ final class UpdatableFontDir {
FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
- // Use preinstalled font config for checking revision number.
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ // Use preinstalled font config for checking revision number.
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ } else {
+ fontConfig = getSystemFontConfig();
+ }
}
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
- // Treat as error if post script name of font family was not installed.
- for (int i = 0; i < config.fontFamilies.size(); ++i) {
- FontUpdateRequest.Family family = config.fontFamilies.get(i);
- for (int j = 0; j < family.getFonts().size(); ++j) {
- FontUpdateRequest.Font font = family.getFonts().get(j);
- if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
- continue;
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ // Treat as error if post script name of font family was not installed.
+ for (int i = 0; i < config.fontFamilies.size(); ++i) {
+ FontUpdateRequest.Family family = config.fontFamilies.get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontUpdateRequest.Font font = family.getFonts().get(j);
+ if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+ continue;
+ }
+
+ if (fontConfig == null) {
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ }
+
+ if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+ continue;
+ }
+
+ Slog.e(TAG, "Unknown font that has PostScript name "
+ + font.getPostScriptName() + " is requested in FontFamily "
+ + family.getName());
+ return;
}
-
- if (fontConfig == null) {
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- }
-
- if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
- continue;
- }
-
- Slog.e(TAG, "Unknown font that has PostScript name "
- + font.getPostScriptName() + " is requested in FontFamily "
- + family.getName());
- return;
}
}
@@ -262,7 +273,9 @@ final class UpdatableFontDir {
mFontFileInfoMap.clear();
mLastModifiedMillis = 0;
FileUtils.deleteContents(mFilesDir);
- mConfigFile.delete();
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ mConfigFile.delete();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 77119d5ac384..f32c11d90c0d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1197,54 +1197,11 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
- }
-
- @Override // Binder call
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
}
@Override // Binder call
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.addKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.removeKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
@@ -1270,11 +1227,6 @@ public class InputManagerService extends IInputManager.Stub
imeInfo, imeSubtype);
}
-
- public void switchKeyboardLayout(int deviceId, int direction) {
- mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
- }
-
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
mNative.setFocusedApplication(displayId, application);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 661008103a25..9ba647fb1d2d 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -60,7 +60,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -69,7 +68,6 @@ import android.view.KeyCharacterMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -96,7 +94,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Stream;
/**
* A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
@@ -112,9 +109,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
- private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
- private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
- private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+ private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2;
+ private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3;
private final Context mContext;
private final NativeInputManagerService mNative;
@@ -126,13 +122,14 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
// Connected keyboards with associated keyboard layouts (either auto-detected or manually
// selected layout).
private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
- private Toast mSwitchedKeyboardLayoutToast;
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
new ArrayMap<>();
+
+ private HashSet<String> mAvailableLayouts = new HashSet<>();
private final Object mImeInfoLock = new Object();
@Nullable
@GuardedBy("mImeInfoLock")
@@ -206,68 +203,51 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
boolean needToShowNotification = false;
- if (!useNewSettingsUi()) {
- synchronized (mDataStore) {
- String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
- if (layout == null) {
- layout = getDefaultKeyboardLayout(inputDevice);
- if (layout != null) {
- setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
- }
- }
- if (layout == null) {
- // In old settings show notification always until user manually selects a
- // layout in the settings.
- needToShowNotification = true;
- }
- }
- } else {
- Set<String> selectedLayouts = new HashSet<>();
- List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
- List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
- boolean hasMissingLayout = false;
- for (ImeInfo imeInfo : imeInfoList) {
- // Check if the layout has been previously configured
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- keyboardIdentifier, imeInfo);
- boolean noLayoutFound = result.getLayoutDescriptor() == null;
- if (!noLayoutFound) {
- selectedLayouts.add(result.getLayoutDescriptor());
- }
- resultList.add(result);
- hasMissingLayout |= noLayoutFound;
+ Set<String> selectedLayouts = new HashSet<>();
+ List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+ List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
+ boolean hasMissingLayout = false;
+ for (ImeInfo imeInfo : imeInfoList) {
+ // Check if the layout has been previously configured
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ keyboardIdentifier, imeInfo);
+ if (result.getLayoutDescriptor() != null) {
+ selectedLayouts.add(result.getLayoutDescriptor());
+ } else {
+ hasMissingLayout = true;
}
+ resultList.add(result);
+ }
- if (DEBUG) {
- Slog.d(TAG,
- "Layouts selected for input device: " + keyboardIdentifier
- + " -> selectedLayouts: " + selectedLayouts);
- }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Layouts selected for input device: " + keyboardIdentifier
+ + " -> selectedLayouts: " + selectedLayouts);
+ }
- // If even one layout not configured properly, we need to ask user to configure
- // the keyboard properly from the Settings.
- if (hasMissingLayout) {
- selectedLayouts.clear();
- }
+ // If even one layout not configured properly, we need to ask user to configure
+ // the keyboard properly from the Settings.
+ if (hasMissingLayout) {
+ selectedLayouts.clear();
+ }
- config.setConfiguredLayouts(selectedLayouts);
+ config.setConfiguredLayouts(selectedLayouts);
- synchronized (mDataStore) {
- try {
- final String key = keyboardIdentifier.toString();
- if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
- // Need to show the notification only if layout selection changed
- // from the previous configuration
- needToShowNotification = true;
- }
+ synchronized (mDataStore) {
+ try {
+ final String key = keyboardIdentifier.toString();
+ if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+ // Need to show the notification only if layout selection changed
+ // from the previous configuration
+ needToShowNotification = true;
+ }
- if (shouldLogConfiguration) {
- logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
- !mDataStore.hasInputDeviceEntry(key));
- }
- } finally {
- mDataStore.saveIfNeeded();
+ if (shouldLogConfiguration) {
+ logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
+ !mDataStore.hasInputDeviceEntry(key));
}
+ } finally {
+ mDataStore.saveIfNeeded();
}
}
if (needToShowNotification) {
@@ -275,63 +255,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
- final Locale systemLocale = mContext.getResources().getConfiguration().locale;
- // If our locale doesn't have a language for some reason, then we don't really have a
- // reasonable default.
- if (TextUtils.isEmpty(systemLocale.getLanguage())) {
- return null;
- }
- final List<KeyboardLayout> layouts = new ArrayList<>();
- visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
- // Only select a default when we know the layout is appropriate. For now, this
- // means it's a custom layout for a specific keyboard.
- if (layout.getVendorId() != inputDevice.getVendorId()
- || layout.getProductId() != inputDevice.getProductId()) {
- return;
- }
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && isCompatibleLocale(systemLocale, locale)) {
- layouts.add(layout);
- break;
- }
- }
- });
-
- if (layouts.isEmpty()) {
- return null;
- }
-
- // First sort so that ones with higher priority are listed at the top
- Collections.sort(layouts);
- // Next we want to try to find an exact match of language, country and variant.
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
- && locale.getVariant().equals(systemLocale.getVariant())) {
- return layout.getDescriptor();
- }
- }
- }
- // Then try an exact match of language and country
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
- return layout.getDescriptor();
- }
- }
- }
-
- // Give up and just use the highest priority layout with matching language
- return layouts.get(0).getDescriptor();
- }
-
private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
// Different languages are never compatible
if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
@@ -343,11 +266,19 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
|| systemLocale.getCountry().equals(keyboardLocale.getCountry());
}
+ @MainThread
private void updateKeyboardLayouts() {
// Scan all input devices state for keyboard layouts that have been uninstalled.
- final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+ final HashSet<String> availableKeyboardLayouts = new HashSet<>();
visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
availableKeyboardLayouts.add(layout.getDescriptor()));
+
+ // If available layouts don't change, there is no need to reload layouts.
+ if (mAvailableLayouts.equals(availableKeyboardLayouts)) {
+ return;
+ }
+ mAvailableLayouts = availableKeyboardLayouts;
+
synchronized (mDataStore) {
try {
mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
@@ -374,53 +305,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
@AnyThread
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- // Provide all supported keyboard layouts since Ime info is not provided
- return getKeyboardLayouts();
- }
- final String[] enabledLayoutDescriptors =
- getEnabledKeyboardLayoutsForInputDevice(identifier);
- final ArrayList<KeyboardLayout> enabledLayouts =
- new ArrayList<>(enabledLayoutDescriptors.length);
- final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
- visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
- boolean mHasSeenDeviceSpecificLayout;
-
- @Override
- public void visitKeyboardLayout(Resources resources,
- int keyboardLayoutResId, KeyboardLayout layout) {
- // First check if it's enabled. If the keyboard layout is enabled then we always
- // want to return it as a possible layout for the device.
- for (String s : enabledLayoutDescriptors) {
- if (s != null && s.equals(layout.getDescriptor())) {
- enabledLayouts.add(layout);
- return;
- }
- }
- // Next find any potential layouts that aren't yet enabled for the device. For
- // devices that have special layouts we assume there's a reason that the generic
- // layouts don't work for them so we don't want to return them since it's likely
- // to result in a poor user experience.
- if (layout.getVendorId() == identifier.getVendorId()
- && layout.getProductId() == identifier.getProductId()) {
- if (!mHasSeenDeviceSpecificLayout) {
- mHasSeenDeviceSpecificLayout = true;
- potentialLayouts.clear();
- }
- potentialLayouts.add(layout);
- } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
- && !mHasSeenDeviceSpecificLayout) {
- potentialLayouts.add(layout);
- }
- }
- });
- return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
- KeyboardLayout[]::new);
- }
-
- @AnyThread
@Nullable
public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -580,195 +464,16 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return LocaleList.forLanguageTags(languageTags.replace('|', ','));
}
- @AnyThread
- @Nullable
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
- return null;
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String layout;
- // try loading it using the layout descriptor if we have it
- layout = mDataStore.getCurrentKeyboardLayout(key);
- if (layout == null && !key.equals(identifier.getDescriptor())) {
- // if it doesn't exist fall back to the device descriptor
- layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (DEBUG) {
- Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
- + identifier.toString() + ": " + layout);
- }
- return layout;
- }
- }
-
- @AnyThread
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
- return;
- }
-
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
- if (DEBUG) {
- Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
- + " key: " + key
- + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
- }
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
- return new String[0];
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String[] layouts = mDataStore.getKeyboardLayouts(key);
- if ((layouts == null || layouts.length == 0)
- && !key.equals(identifier.getDescriptor())) {
- layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
- }
- return layouts;
- }
- }
-
- @AnyThread
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
- && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
- if (!key.equals(identifier.getDescriptor())) {
- // We need to remove from both places to ensure it is gone
- removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
- keyboardLayoutDescriptor);
- }
- if (removed && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void switchKeyboardLayout(int deviceId, int direction) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "switchKeyboardLayout API not supported");
- return;
- }
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
- }
-
- @MainThread
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
- final InputDevice device = getInputDevice(deviceId);
- if (device != null) {
- final boolean changed;
- final String keyboardLayoutDescriptor;
-
- String key = new KeyboardIdentifier(device.getIdentifier()).toString();
- synchronized (mDataStore) {
- try {
- changed = mDataStore.switchKeyboardLayout(key, direction);
- keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
- key);
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
-
- if (changed) {
- if (mSwitchedKeyboardLayoutToast != null) {
- mSwitchedKeyboardLayoutToast.cancel();
- mSwitchedKeyboardLayoutToast = null;
- }
- if (keyboardLayoutDescriptor != null) {
- KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
- if (keyboardLayout != null) {
- mSwitchedKeyboardLayoutToast = Toast.makeText(
- mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
- mSwitchedKeyboardLayoutToast.show();
- }
- }
-
- reloadKeyboardLayouts();
- }
- }
- }
-
@Nullable
@AnyThread
public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
String layoutType) {
String keyboardLayoutDescriptor;
- if (useNewSettingsUi()) {
- synchronized (mImeInfoLock) {
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- new KeyboardIdentifier(identifier, languageTag, layoutType),
- mCurrentImeInfo);
- keyboardLayoutDescriptor = result.getLayoutDescriptor();
- }
- } else {
- keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+ synchronized (mImeInfoLock) {
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ new KeyboardIdentifier(identifier, languageTag, layoutType),
+ mCurrentImeInfo);
+ keyboardLayoutDescriptor = result.getLayoutDescriptor();
}
if (keyboardLayoutDescriptor == null) {
return null;
@@ -797,10 +502,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
- return FAILED;
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return FAILED;
@@ -820,10 +521,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype,
String keyboardLayoutDescriptor) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
- return;
- }
Objects.requireNonNull(keyboardLayoutDescriptor,
"keyboardLayoutDescriptor must not be null");
InputDevice inputDevice = getInputDevice(identifier);
@@ -854,10 +551,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
- return new KeyboardLayout[0];
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return new KeyboardLayout[0];
@@ -923,10 +616,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public void onInputMethodSubtypeChanged(@UserIdInt int userId,
@Nullable InputMethodSubtypeHandle subtypeHandle,
@Nullable InputMethodSubtype subtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
- return;
- }
if (subtypeHandle == null) {
if (DEBUG) {
Slog.d(TAG, "No InputMethod is running, ignoring change");
@@ -1289,9 +978,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
onInputDeviceAdded(deviceId);
}
return true;
- case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
- return true;
case MSG_RELOAD_KEYBOARD_LAYOUTS:
reloadKeyboardLayouts();
return true;
@@ -1303,10 +989,6 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- private boolean useNewSettingsUi() {
- return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
- }
-
@Nullable
private InputDevice getInputDevice(int deviceId) {
InputManager inputManager = mContext.getSystemService(InputManager.class);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 31083fd5de03..7859253d66e0 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,7 +27,6 @@ import android.util.Xml;
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -42,7 +41,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -74,7 +72,7 @@ final class PersistentDataStore {
new HashMap<String, InputDeviceState>();
// The interface for methods which should be replaced by the test harness.
- private Injector mInjector;
+ private final Injector mInjector;
// True if the data has been loaded.
private boolean mLoaded;
@@ -83,7 +81,7 @@ final class PersistentDataStore {
private boolean mDirty;
// Storing key remapping
- private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+ private final Map<Integer, Integer> mKeyRemapping = new HashMap<>();
public PersistentDataStore() {
this(new Injector());
@@ -130,22 +128,6 @@ final class PersistentDataStore {
}
@Nullable
- public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- return state != null ? state.getCurrentKeyboardLayout() : null;
- }
-
- public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- @Nullable
public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
return state != null ? state.getKeyboardLayout(key) : null;
@@ -171,43 +153,6 @@ final class PersistentDataStore {
return false;
}
- public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state == null) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return state.getKeyboardLayouts();
- }
-
- public boolean addKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean removeKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state != null && state.switchKeyboardLayout(direction)) {
- setDirty();
- return true;
- }
- return false;
- }
-
public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
int brightness) {
InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
@@ -417,9 +362,6 @@ final class PersistentDataStore {
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
- @Nullable
- private String mCurrentKeyboardLayout;
- private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
@@ -465,49 +407,6 @@ final class PersistentDataStore {
return true;
}
- @Nullable
- public String getCurrentKeyboardLayout() {
- return mCurrentKeyboardLayout;
- }
-
- public boolean setCurrentKeyboardLayout(String keyboardLayout) {
- if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) {
- return false;
- }
- addKeyboardLayout(keyboardLayout);
- mCurrentKeyboardLayout = keyboardLayout;
- return true;
- }
-
- public String[] getKeyboardLayouts() {
- if (mKeyboardLayouts.isEmpty()) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
- }
-
- public boolean addKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index >= 0) {
- return false;
- }
- mKeyboardLayouts.add(-index - 1, keyboardLayout);
- if (mCurrentKeyboardLayout == null) {
- mCurrentKeyboardLayout = keyboardLayout;
- }
- return true;
- }
-
- public boolean removeKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index < 0) {
- return false;
- }
- mKeyboardLayouts.remove(index);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
- return true;
- }
-
public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
return false;
@@ -521,48 +420,8 @@ final class PersistentDataStore {
return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
}
- private void updateCurrentKeyboardLayoutIfRemoved(
- String removedKeyboardLayout, int removedIndex) {
- if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
- if (!mKeyboardLayouts.isEmpty()) {
- int index = removedIndex;
- if (index == mKeyboardLayouts.size()) {
- index = 0;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- } else {
- mCurrentKeyboardLayout = null;
- }
- }
- }
-
- public boolean switchKeyboardLayout(int direction) {
- final int size = mKeyboardLayouts.size();
- if (size < 2) {
- return false;
- }
- int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
- assert index >= 0;
- if (direction > 0) {
- index = (index + 1) % size;
- } else {
- index = (index + size - 1) % size;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- return true;
- }
-
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
- for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
- String keyboardLayout = mKeyboardLayouts.get(i);
- if (!availableKeyboardLayouts.contains(keyboardLayout)) {
- Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
- mKeyboardLayouts.remove(i);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
- changed = true;
- }
- }
List<String> removedEntries = new ArrayList<>();
for (String key : mKeyboardLayoutMap.keySet()) {
if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
@@ -582,27 +441,7 @@ final class PersistentDataStore {
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("keyboard-layout")) {
- String descriptor = parser.getAttributeValue(null, "descriptor");
- if (descriptor == null) {
- throw new XmlPullParserException(
- "Missing descriptor attribute on keyboard-layout.");
- }
- String current = parser.getAttributeValue(null, "current");
- if (mKeyboardLayouts.contains(descriptor)) {
- throw new XmlPullParserException(
- "Found duplicate keyboard layout.");
- }
-
- mKeyboardLayouts.add(descriptor);
- if (current != null && current.equals("true")) {
- if (mCurrentKeyboardLayout != null) {
- throw new XmlPullParserException(
- "Found multiple current keyboard layouts.");
- }
- mCurrentKeyboardLayout = descriptor;
- }
- } else if (parser.getName().equals("keyed-keyboard-layout")) {
+ if (parser.getName().equals("keyed-keyboard-layout")) {
String key = parser.getAttributeValue(null, "key");
if (key == null) {
throw new XmlPullParserException(
@@ -676,27 +515,9 @@ final class PersistentDataStore {
}
}
}
-
- // Maintain invariant that layouts are sorted.
- Collections.sort(mKeyboardLayouts);
-
- // Maintain invariant that there is always a current keyboard layout unless
- // there are none installed.
- if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
- mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
- }
}
public void saveToXml(TypedXmlSerializer serializer) throws IOException {
- for (String layout : mKeyboardLayouts) {
- serializer.startTag(null, "keyboard-layout");
- serializer.attribute(null, "descriptor", layout);
- if (layout.equals(mCurrentKeyboardLayout)) {
- serializer.attributeBoolean(null, "current", true);
- }
- serializer.endTag(null, "keyboard-layout");
- }
-
for (String key : mKeyboardLayoutMap.keySet()) {
serializer.startTag(null, "keyed-keyboard-layout");
serializer.attribute(null, "key", key);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c80f988bd61b..593174420d94 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -107,7 +107,7 @@ import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.AndroidKeyStoreMaintenance;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -293,6 +293,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private final SyntheticPasswordManager mSpManager;
private final KeyStore mKeyStore;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
@@ -627,6 +628,10 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+ public KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
+ }
+
public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
return new UnifiedProfilePasswordCache(ks);
}
@@ -650,6 +655,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mInjector = injector;
mContext = injector.getContext();
mKeyStore = injector.getKeyStore();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler(injector.getServiceThread());
mStrongAuth = injector.getStrongAuth();
@@ -1460,7 +1466,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void unlockKeystore(int userId, SyntheticPassword sp) {
- Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
+ mKeyStoreAuthorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
}
@VisibleForTesting /** Note: this method is overridden in unit tests */
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index e7f717aa6c3b..f27ade42a738 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -136,7 +136,7 @@ import java.util.Objects;
mBluetoothRouteController =
new BluetoothDeviceRoutesManager(
- mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
+ mContext, mHandler, btAdapter, this::rebuildAvailableRoutesAndNotify);
// Just build routes but don't notify. The caller may not expect the listener to be invoked
// before this constructor has finished executing.
rebuildAvailableRoutes();
@@ -204,23 +204,24 @@ import java.util.Objects;
Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId);
return;
}
- // TODO: b/329929065 - Push audio manager and bluetooth operations to the handler, so that
- // they don't run on a binder thread, so as to prevent possible deadlocks (these operations
- // may need system_server binder threads to complete).
- if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
- // By default, the last connected device is the active route so we don't need to apply a
- // routing audio policy.
- mBluetoothRouteController.activateBluetoothDeviceWithAddress(
- mediaRoute2InfoHolder.mMediaRoute2Info.getAddress());
- mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
- } else {
- AudioDeviceAttributes attr =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT,
- mediaRoute2InfoHolder.mAudioDeviceInfoType,
- /* address= */ ""); // This is not a BT device, hence no address needed.
- mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr);
- }
+ Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder);
+ Runnable guardedTransferAction =
+ () -> {
+ try {
+ transferAction.run();
+ } catch (Throwable throwable) {
+ // We swallow the exception to avoid crashing system_server, since this
+ // doesn't run on a binder thread.
+ Slog.e(
+ TAG,
+ "Unexpected exception while transferring to route id: " + routeId,
+ throwable);
+ mHandler.post(this::rebuildAvailableRoutesAndNotify);
+ }
+ };
+ // We post the transfer operation to the handler to avoid making these calls on a binder
+ // thread. See class javadoc for details.
+ mHandler.post(guardedTransferAction);
}
@RequiresPermission(
@@ -236,6 +237,28 @@ import java.util.Objects;
return true;
}
+ private Runnable getTransferActionForRoute(MediaRoute2InfoHolder mediaRoute2InfoHolder) {
+ if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
+ String deviceAddress = mediaRoute2InfoHolder.mMediaRoute2Info.getAddress();
+ return () -> {
+ // By default, the last connected device is the active route so we don't
+ // need to apply a routing audio policy.
+ mBluetoothRouteController.activateBluetoothDeviceWithAddress(deviceAddress);
+ mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+ };
+
+ } else {
+ AudioDeviceAttributes deviceAttributes =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ mediaRoute2InfoHolder.mAudioDeviceInfoType,
+ /* address= */ ""); // This is not a BT device, hence no address needed.
+ return () ->
+ mAudioManager.setPreferredDeviceForStrategy(
+ mStrategyForMedia, deviceAttributes);
+ }
+ }
+
@RequiresPermission(
anyOf = {
Manifest.permission.MODIFY_AUDIO_ROUTING,
diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
index b881ef6195d5..8b65ea305ad8 100644
--- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
+++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
@@ -31,6 +31,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaRoute2Info;
+import android.os.Handler;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -77,26 +78,35 @@ import java.util.stream.Collectors;
@NonNull
private final Context mContext;
- @NonNull
- private final BluetoothAdapter mBluetoothAdapter;
+ @NonNull private final Handler mHandler;
+ @NonNull private final BluetoothAdapter mBluetoothAdapter;
@NonNull
private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
@NonNull
private final BluetoothProfileMonitor mBluetoothProfileMonitor;
- BluetoothDeviceRoutesManager(@NonNull Context context,
+ BluetoothDeviceRoutesManager(
+ @NonNull Context context,
+ @NonNull Handler handler,
@NonNull BluetoothAdapter bluetoothAdapter,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
- this(context, bluetoothAdapter,
- new BluetoothProfileMonitor(context, bluetoothAdapter), listener);
+ this(
+ context,
+ handler,
+ bluetoothAdapter,
+ new BluetoothProfileMonitor(context, bluetoothAdapter),
+ listener);
}
@VisibleForTesting
- BluetoothDeviceRoutesManager(@NonNull Context context,
+ BluetoothDeviceRoutesManager(
+ @NonNull Context context,
+ @NonNull Handler handler,
@NonNull BluetoothAdapter bluetoothAdapter,
@NonNull BluetoothProfileMonitor bluetoothProfileMonitor,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
mContext = Objects.requireNonNull(context);
+ mHandler = handler;
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor);
mListener = Objects.requireNonNull(listener);
@@ -298,6 +308,26 @@ import java.util.stream.Collectors;
};
}
+ private void handleBluetoothAdapterStateChange(int state) {
+ if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ synchronized (BluetoothDeviceRoutesManager.this) {
+ mBluetoothRoutes.clear();
+ }
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ updateBluetoothRoutes();
+
+ boolean shouldCallListener;
+ synchronized (BluetoothDeviceRoutesManager.this) {
+ shouldCallListener = !mBluetoothRoutes.isEmpty();
+ }
+
+ if (shouldCallListener) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
private MediaRoute2Info mRoute;
@@ -308,23 +338,10 @@ import java.util.stream.Collectors;
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
- if (state == BluetoothAdapter.STATE_OFF
- || state == BluetoothAdapter.STATE_TURNING_OFF) {
- synchronized (BluetoothDeviceRoutesManager.this) {
- mBluetoothRoutes.clear();
- }
- notifyBluetoothRoutesUpdated();
- } else if (state == BluetoothAdapter.STATE_ON) {
- updateBluetoothRoutes();
-
- boolean shouldCallListener;
- synchronized (BluetoothDeviceRoutesManager.this) {
- shouldCallListener = !mBluetoothRoutes.isEmpty();
- }
-
- if (shouldCallListener) {
- notifyBluetoothRoutesUpdated();
- }
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(() -> handleBluetoothAdapterStateChange(state));
+ } else {
+ handleBluetoothAdapterStateChange(state);
}
}
}
@@ -337,8 +354,16 @@ import java.util.stream.Collectors;
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
case BluetoothDevice.ACTION_ALIAS_CHANGED:
- updateBluetoothRoutes();
- notifyBluetoothRoutesUpdated();
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(
+ () -> {
+ updateBluetoothRoutes();
+ notifyBluetoothRoutesUpdated();
+ });
+ } else {
+ updateBluetoothRoutes();
+ notifyBluetoothRoutesUpdated();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
index 66cafabb1e6e..e4f2ec36d758 100644
--- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java
+++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
@@ -44,7 +44,6 @@ import java.util.Map;
* Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
* without any parameters.
*/
-// TODO: Move this class to apex/media/
public abstract class MediaKeyDispatcher {
@IntDef(flag = true, value = {
KEY_EVENT_SINGLE_TAP,
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 67d3fe995160..db83d4b76778 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -232,7 +232,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
if (!mRunning) {
return false;
}
- if (!getSessionInfos().isEmpty() || mIsManagerScanning) {
+ boolean bindDueToManagerScan =
+ mIsManagerScanning && Flags.enablePreventionOfManagerScansWhenNoAppsScan();
+ if (!getSessionInfos().isEmpty() || bindDueToManagerScan) {
// We bind if any manager is scanning (regardless of whether an app is scanning) to give
// the opportunity for providers to publish routing sessions that were established
// directly between the app and the provider (typically via AndroidX MediaRouter). See
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ec15ff35676c..e50189b2e79d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -114,6 +114,7 @@ class MediaRouter2ServiceImpl {
};
private final Context mContext;
+ private final Looper mLooper;
private final UserManagerInternal mUserManagerInternal;
private final Object mLock = new Object();
private final AppOpsManager mAppOpsManager;
@@ -178,8 +179,9 @@ class MediaRouter2ServiceImpl {
Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
Manifest.permission.WATCH_APPOPS
})
- /* package */ MediaRouter2ServiceImpl(Context context) {
+ /* package */ MediaRouter2ServiceImpl(@NonNull Context context, @NonNull Looper looper) {
mContext = context;
+ mLooper = looper;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
@@ -187,12 +189,10 @@ class MediaRouter2ServiceImpl {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- if (!Flags.disableScreenOffBroadcastReceiver()) {
- IntentFilter screenOnOffIntentFilter = new IntentFilter();
- screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
- screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
- mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
- }
+ IntentFilter screenOnOffIntentFilter = new IntentFilter();
+ screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
+ screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
// Passing null package name to listen to all events.
mAppOpsManager.startWatchingMode(
@@ -1891,7 +1891,7 @@ class MediaRouter2ServiceImpl {
private UserRecord getOrCreateUserRecordLocked(int userId) {
UserRecord userRecord = mUserRecords.get(userId);
if (userRecord == null) {
- userRecord = new UserRecord(userId);
+ userRecord = new UserRecord(userId, mLooper);
mUserRecords.put(userId, userRecord);
userRecord.init();
if (isUserActiveLocked(userId)) {
@@ -1962,9 +1962,13 @@ class MediaRouter2ServiceImpl {
Set<String> mActivelyScanningPackages = Set.of();
final UserHandler mHandler;
- UserRecord(int userId) {
+ UserRecord(int userId, @NonNull Looper looper) {
mUserId = userId;
- mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
+ mHandler =
+ new UserHandler(
+ /* service= */ MediaRouter2ServiceImpl.this,
+ /* userRecord= */ this,
+ looper);
}
void init() {
@@ -2365,12 +2369,16 @@ class MediaRouter2ServiceImpl {
private boolean mRunning;
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
- UserHandler(@NonNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord) {
- super(Looper.getMainLooper(), null, true);
+ UserHandler(
+ @NonNull MediaRouter2ServiceImpl service,
+ @NonNull UserRecord userRecord,
+ @NonNull Looper looper) {
+ super(looper, /* callback= */ null, /* async= */ true);
mServiceRef = new WeakReference<>(service);
mUserRecord = userRecord;
- mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
- UserHandle.of(userRecord.mUserId));
+ mSystemProvider =
+ new SystemMediaRoute2Provider(
+ service.mContext, UserHandle.of(userRecord.mUserId), looper);
mRouteProviders.add(mSystemProvider);
mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
this, mUserRecord.mUserId);
@@ -3425,9 +3433,7 @@ class MediaRouter2ServiceImpl {
@NonNull
private static List<RouterRecord> getIndividuallyActiveRouters(
MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
- if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()
- && !Flags.enableScreenOffScanning()) {
+ if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
return Collections.emptyList();
}
@@ -3443,9 +3449,7 @@ class MediaRouter2ServiceImpl {
private static boolean areManagersScanning(
MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
- if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()
- && !Flags.enableScreenOffScanning()) {
+ if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
return false;
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 76b8db685f52..1a129cb080a8 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -53,6 +53,7 @@ import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -70,6 +71,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
+import com.android.media.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.UserManagerInternal;
@@ -94,6 +96,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
implements Watchdog.Monitor {
private static final String TAG = "MediaRouterService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String WORKER_THREAD_NAME = "MediaRouterServiceThread";
/**
* Timeout in milliseconds for a selected route to transition from a disconnected state to a
@@ -110,6 +113,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private static final long CONNECTED_TIMEOUT = 60000;
private final Context mContext;
+ private final Looper mLooper;
// State guarded by mLock.
private final Object mLock = new Object();
@@ -125,7 +129,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler;
private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
@@ -141,7 +145,15 @@ public final class MediaRouterService extends IMediaRouterService.Stub
@RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
public MediaRouterService(Context context) {
- mService2 = new MediaRouter2ServiceImpl(context);
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ HandlerThread handlerThread = new HandlerThread(WORKER_THREAD_NAME);
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ } else {
+ mLooper = Looper.myLooper();
+ }
+ mHandler = new Handler(mLooper);
+ mService2 = new MediaRouter2ServiceImpl(context, mLooper);
mContext = context;
Watchdog.getInstance().addMonitor(this);
Resources res = context.getResources();
@@ -1104,7 +1116,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
public UserRecord(int userId) {
mUserId = userId;
- mHandler = new UserHandler(MediaRouterService.this, this);
+ mHandler = new UserHandler(MediaRouterService.this, this, mLooper);
}
public void dump(final PrintWriter pw, String prefix) {
@@ -1212,8 +1224,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private long mConnectionTimeoutStartTime;
private boolean mClientStateUpdateScheduled;
- public UserHandler(MediaRouterService service, UserRecord userRecord) {
- super(Looper.getMainLooper(), null, true);
+ private UserHandler(MediaRouterService service, UserRecord userRecord, Looper looper) {
+ super(looper, null, true);
mService = service;
mUserRecord = userRecord;
mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 0cd7654f70ea..dfb2b0a750e3 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -157,6 +157,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
+ public void expireTempEngaged() {
+ // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+ }
+
+ @Override
public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
KeyEvent ke, int sequenceId, ResultReceiver cb) {
// TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index eb4e6e44c8a4..8f164d361a56 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -24,6 +24,7 @@ import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_L
import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -85,6 +86,8 @@ import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -225,6 +228,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private int mPolicies;
+ private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
+
+ @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface UserEngagementState {}
+
+ /**
+ * Indicates that the session is active and in one of the user engaged states.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_PERMANENTLY_ENGAGED = 0;
+
+ /**
+ * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_TEMPORARY_ENGAGED = 1;
+
+ /**
+ * Indicates that the session is either not active or in one of the user disengaged states
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_DISENGAGED = 2;
+
+ /**
+ * Indicates the duration of the temporary engaged states.
+ *
+ * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
+ * engaged, meaning the corresponding session is only considered in an engaged state for the
+ * duration of this timeout, and only if coming from an engaged state.
+ *
+ * <p>For example, if a session is transitioning from a user-engaged state {@link
+ * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
+ * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
+ * the duration of this timeout, starting at the transition instant. However, a temporary
+ * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
+ * state {@link PlaybackState#STATE_STOPPED}.
+ */
+ private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000;
+
public MediaSessionRecord(
int ownerPid,
int ownerUid,
@@ -548,6 +594,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
mDestroyed = true;
mPlaybackState = null;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
mHandler.post(MessageHandler.MSG_DESTROYED);
}
}
@@ -559,6 +606,12 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
+ @Override
+ public void expireTempEngaged() {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ }
+
/**
* Sends media button.
*
@@ -849,7 +902,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -876,7 +929,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -911,7 +964,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -938,7 +991,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -965,7 +1018,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -992,7 +1045,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1017,7 +1070,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1042,7 +1095,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
// After notifying clear all listeners
- mControllerCallbackHolders.clear();
+ removeControllerHoldersSafely(null);
}
private PlaybackState getStateWithUpdatedPosition() {
@@ -1090,6 +1143,17 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return -1;
}
+ private void removeControllerHoldersSafely(
+ Collection<ISessionControllerCallbackHolder> holders) {
+ synchronized (mLock) {
+ if (holders == null) {
+ mControllerCallbackHolders.clear();
+ } else {
+ mControllerCallbackHolders.removeAll(holders);
+ }
+ }
+ }
+
private PlaybackInfo getVolumeAttributes() {
int volumeType;
AudioAttributes attributes;
@@ -1118,6 +1182,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
};
+ private final Runnable mHandleTempEngagedSessionTimeout =
+ () -> {
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ };
+
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private static boolean componentNameExists(
@NonNull ComponentName componentName, @NonNull Context context, int userId) {
@@ -1134,6 +1203,40 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return !resolveInfos.isEmpty();
}
+ private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+ int oldUserEngagedState = mUserEngagementState;
+ int newUserEngagedState;
+ if (!isActive() || mPlaybackState == null) {
+ newUserEngagedState = USER_DISENGAGED;
+ } else if (isActive() && mPlaybackState.isActive()) {
+ newUserEngagedState = USER_PERMANENTLY_ENGAGED;
+ } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
+ newUserEngagedState =
+ oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
+ ? USER_TEMPORARY_ENGAGED
+ : USER_DISENGAGED;
+ } else {
+ newUserEngagedState = USER_DISENGAGED;
+ }
+ if (oldUserEngagedState == newUserEngagedState) {
+ return;
+ }
+
+ if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT);
+ } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ }
+
+ boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED;
+ boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED;
+ mUserEngagementState = newUserEngagedState;
+ if (wasUserEngaged != isNowUserEngaged) {
+ mService.onSessionUserEngagementStateChange(
+ /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged);
+ }
+ }
+
private final class SessionStub extends ISession.Stub {
@Override
public void destroySession() throws RemoteException {
@@ -1171,8 +1274,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
.logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
callingUid, callingPid);
}
-
- mIsActive = active;
+ synchronized (mLock) {
+ mIsActive = active;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+ }
long token = Binder.clearCallingIdentity();
try {
mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
@@ -1330,6 +1435,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
&& TRANSITION_PRIORITY_STATES.contains(newState));
synchronized (mLock) {
mPlaybackState = state;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
}
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 09991995099e..b57b14835987 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -196,6 +196,12 @@ public abstract class MediaSessionRecordImpl {
*/
public abstract boolean isClosed();
+ /**
+ * Note: This method is only used for testing purposes If the session is temporary engaged, the
+ * timeout will expire and it will become disengaged.
+ */
+ public abstract void expireTempEngaged();
+
@Override
public final boolean equals(Object o) {
if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 53c32cf31d21..2ca76a578f53 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -367,11 +367,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionActiveStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionActiveStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
mHandler.postSessionsChanged(record);
}
@@ -479,11 +481,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionPlaybackStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionPlaybackStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
}
}
@@ -650,33 +654,54 @@ public class MediaSessionService extends SystemService implements Monitor {
session.close();
Log.d(TAG, "destroySessionLocked: record=" + session);
- setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+
reportMediaInteractionEvent(session, /* userEngaged= */ false);
mHandler.postSessionsChanged(session);
}
- private void setForegroundServiceAllowance(
- MediaSessionRecordImpl record, boolean allowRunningInForeground) {
- if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
- return;
- }
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- record.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return;
- }
- if (allowRunningInForeground) {
- onUserSessionEngaged(record);
+ void onSessionUserEngagementStateChange(
+ MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
+ if (isUserEngaged) {
+ addUserEngagedSession(mediaSessionRecord);
+ startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
} else {
- onUserDisengaged(record);
+ removeUserEngagedSession(mediaSessionRecord);
+ stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
}
}
- private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>());
mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord);
+ }
+ }
+
+ private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
+ Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs =
+ mUserEngagedSessionsForFgs.get(uid);
+ if (mUidUserEngagedSessionsForFgs == null) {
+ return;
+ }
+
+ mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord);
+ if (mUidUserEngagedSessionsForFgs.isEmpty()) {
+ mUserEngagedSessionsForFgs.remove(uid);
+ }
+ }
+ }
+
+ private void startFgsIfSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
mActivityManagerInternal.startForegroundServiceDelegate(
@@ -688,30 +713,34 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void stopFgsIfNoSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- if (mUserEngagedSessionsForFgs.containsKey(uid)) {
- mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord);
- if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) {
- mUserEngagedSessionsForFgs.remove(uid);
- }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ mediaSessionRecord.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl sessionRecord :
+ for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid,
- Set.of())) {
- if (sessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
+ for (Notification mediaNotification :
+ mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (record.isLinkedToNotification(mediaNotification)) {
+ // A user engaged session linked with a media notification is found.
+ // We shouldn't call stop FGS in this case.
+ return;
}
}
}
- if (shouldStopFgs) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- mediaSessionRecord.getForegroundServiceDelegationOptions());
- }
+
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
}
}
@@ -2502,7 +2531,6 @@ public class MediaSessionService extends SystemService implements Monitor {
}
MediaSessionRecord session = null;
MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
-
if (mCustomMediaKeyDispatcher != null) {
MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(
keyEvent, uid, asSystemService);
@@ -2630,6 +2658,18 @@ public class MediaSessionService extends SystemService implements Monitor {
&& streamType <= AudioManager.STREAM_NOTIFICATION;
}
+ @Override
+ public void expireTempEngagedSessions() {
+ synchronized (mLock) {
+ for (Set<MediaSessionRecordImpl> uidSessions :
+ mUserEngagedSessionsForFgs.values()) {
+ for (MediaSessionRecordImpl sessionRecord : uidSessions) {
+ sessionRecord.expireTempEngaged();
+ }
+ }
+ }
+ }
+
private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
private final String mPackageName;
private final int mPid;
@@ -3127,7 +3167,6 @@ public class MediaSessionService extends SystemService implements Monitor {
super.onNotificationPosted(sbn);
Notification postedNotification = sbn.getNotification();
int uid = sbn.getUid();
-
if (!postedNotification.isMediaNotification()) {
return;
}
@@ -3138,8 +3177,8 @@ public class MediaSessionService extends SystemService implements Monitor {
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (mediaSessionRecord.isLinkedToNotification(postedNotification)
- && foregroundServiceDelegationOptions != null) {
+ if (foregroundServiceDelegationOptions != null
+ && mediaSessionRecord.isLinkedToNotification(postedNotification)) {
mActivityManagerInternal.startForegroundServiceDelegate(
foregroundServiceDelegationOptions,
/* connection= */ null);
@@ -3173,21 +3212,7 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl mediaSessionRecord :
- mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
- }
- }
- }
- if (shouldStopFgs
- && notificationRecord.getForegroundServiceDelegationOptions() != null) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- notificationRecord.getForegroundServiceDelegationOptions());
- }
+ stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
}
}
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a56380827f2c..a20de3198d2c 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -92,6 +92,8 @@ public class MediaShellCommand extends ShellCommand {
runMonitor();
} else if (cmd.equals("volume")) {
runVolume();
+ } else if (cmd.equals("expire-temp-engaged-sessions")) {
+ expireTempEngagedSessions();
} else {
showError("Error: unknown command '" + cmd + "'");
return -1;
@@ -367,4 +369,8 @@ public class MediaShellCommand extends ShellCommand {
private void runVolume() throws Exception {
VolumeCtrl.run(this);
}
+
+ private void expireTempEngagedSessions() throws Exception {
+ mSessionService.expireTempEngagedSessions();
+ }
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 67bc61c3b9f6..8df38a8d565a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -86,12 +86,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@GuardedBy("mTransferLock")
@Nullable private volatile SessionCreationRequest mPendingTransferRequest;
- SystemMediaRoute2Provider(Context context, UserHandle user) {
+ SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
super(COMPONENT_NAME);
mIsSystemRouteProvider = true;
mContext = context;
mUser = user;
- Looper looper = Looper.getMainLooper();
mHandler = new Handler(looper);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -654,9 +653,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
return;
}
- // TODO: b/310145678 - Post this to mHandler once mHandler does not run on the main
- // thread.
- updateVolume();
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(SystemMediaRoute2Provider.this::updateVolume);
+ } else {
+ updateVolume();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index b3e5b9e96889..43e2afd8827d 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -2,9 +2,7 @@
"presubmit": [
{
"name": "CtsMediaBetterTogetherTestCases"
- }
- ],
- "postsubmit": [
+ },
{
"name": "MediaRouterServiceTests"
}
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
index 71d8e6ba367e..3ac2d232dfc8 100644
--- a/services/core/java/com/android/server/net/Android.bp
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "net_flags",
package: "com.android.server.net",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 419665a0a5ab..d9491de52d87 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.net"
+container: "system"
flag {
name: "network_blocked_for_top_sleeping_and_above"
diff --git a/services/core/java/com/android/server/notification/Android.bp b/services/core/java/com/android/server/notification/Android.bp
index 9be43581aed5..d757470d86e9 100644
--- a/services/core/java/com/android/server/notification/Android.bp
+++ b/services/core/java/com/android/server/notification/Android.bp
@@ -10,6 +10,7 @@ java_aconfig_library {
aconfig_declarations {
name: "notification_flags",
package: "com.android.server.notification",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index e1e2b3efd1eb..3949dfe01761 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1617,7 +1617,8 @@ abstract public class ManagedServices {
intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
- activityOptions.setIgnorePendingIntentCreatorForegroundState(true);
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
final PendingIntent pendingIntent = PendingIntent.getActivity(
mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE,
activityOptions.toBundle());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5fb66d08d1ee..956e10c79246 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -24,7 +24,6 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
-import static android.app.Flags.updateRankingTime;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
@@ -590,6 +589,8 @@ public class NotificationManagerService extends SystemService {
private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
+ static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis();
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -1319,7 +1320,29 @@ public class NotificationManagerService extends SystemService {
// Notifications that have been interacted with should no longer be lifetime
// extended.
if (lifetimeExtensionRefactor()) {
- r.getSbn().getNotification().flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ // This cancellation should only work if
+ // the notification still has FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
+ // We wait for 200 milliseconds before posting the cancel, to allow the app
+ // time to update the notification in response instead.
+ // If that update goes through, the notification won't have the lifetime
+ // extended flag, and this cancellation will be dropped.
+ mHandler.scheduleCancelNotification(
+ new CancelNotificationRunnable(
+ callingUid,
+ callingPid,
+ r.getSbn().getPackageName(),
+ r.getSbn().getTag(),
+ r.getSbn().getId(),
+ FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY /*=mustHaveFlags*/,
+ FLAG_NO_DISMISS /*=mustNotHaveFlags*/,
+ false /*=sendDelete*/,
+ r.getUserId(),
+ REASON_CLICK,
+ -1 /*=rank*/,
+ -1 /*=count*/,
+ null /*=listener*/,
+ SystemClock.elapsedRealtime()),
+ 200);
}
}
}
@@ -1832,14 +1855,6 @@ public class NotificationManagerService extends SystemService {
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
| FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
true, record.getUserId(), REASON_TIMEOUT, null);
- // If cancellation will be prevented due to lifetime extension, we send an
- // update to system UI.
- final int packageImportance = getPackageImportanceWithIdentity(
- record.getSbn().getPackageName());
- synchronized (mNotificationLock) {
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
- record.getSbn().getPackageName(), packageImportance);
- }
} else {
cancelNotification(record.getSbn().getUid(),
record.getSbn().getInitialPid(),
@@ -3650,15 +3665,6 @@ public class NotificationManagerService extends SystemService {
if (lifetimeExtensionRefactor()) {
// Also don't allow client apps to cancel lifetime extended notifs.
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
- // If cancellation will be prevented due to lifetime extension, we send an update to
- // system UI.
- NotificationRecord record = null;
- final int packageImportance = getPackageImportanceWithIdentity(pkg);
- synchronized (mNotificationLock) {
- record = findNotificationLocked(pkg, tag, id, userId);
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg,
- packageImportance);
- }
}
cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(),
@@ -4855,7 +4861,7 @@ public class NotificationManagerService extends SystemService {
|| isNotificationRecent(r.getUpdateTimeMs());
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
- r.getSbn().getId(), userId, reason, packageImportance);
+ r.getSbn().getId(), userId, reason);
}
} else {
for (NotificationRecord notificationRecord : mNotificationList) {
@@ -4995,14 +5001,10 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId,
- int reason, int packageImportance) {
+ int reason) {
int mustNotHaveFlags = FLAG_ONGOING_EVENT;
if (lifetimeExtensionRefactor()) {
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
- // If cancellation will be prevented due to lifetime extension, we send an update
- // to system UI.
- NotificationRecord record = findNotificationLocked(pkg, tag, id, userId);
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg, packageImportance);
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
mustNotHaveFlags,
@@ -5145,13 +5147,7 @@ public class NotificationManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
- final int packageImportance;
try {
- if (lifetimeExtensionRefactor()) {
- packageImportance = getPackageImportanceWithIdentity(pkg);
- } else {
- packageImportance = IMPORTANCE_NONE;
- }
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
int cancelReason = REASON_LISTENER_CANCEL;
@@ -5164,7 +5160,7 @@ public class NotificationManagerService extends SystemService {
+ " use cancelNotification(key) instead.");
} else {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
- pkg, tag, id, info.userid, cancelReason, packageImportance);
+ pkg, tag, id, info.userid, cancelReason);
}
}
} finally {
@@ -7581,6 +7577,12 @@ public class NotificationManagerService extends SystemService {
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
+
+ if (Flags.allNotifsNeedTtl()) {
+ if (notification.getTimeoutAfter() == 0) {
+ notification.setTimeoutAfter(NOTIFICATION_TTL);
+ }
+ }
}
/**
@@ -8149,19 +8151,30 @@ public class NotificationManagerService extends SystemService {
EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag,
mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName);
}
-
+ int packageImportance = IMPORTANCE_NONE;
+ if (lifetimeExtensionRefactor()) {
+ packageImportance = getPackageImportanceWithIdentity(mPkg);
+ }
synchronized (mNotificationLock) {
// Look for the notification, searching both the posted and enqueued lists.
NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
+
if (r != null) {
// The notification was found, check if it should be removed.
-
// Ideally we'd do this in the caller of this method. However, that would
// require the caller to also find the notification.
if (mReason == REASON_CLICK) {
mUsageStats.registerClickedByUser(r);
}
+ // If cancellation will be prevented due to lifetime extension, we need to
+ // send an update to system UI. This must be checked before flags are checked.
+ // We do not want to send this update.
+ if (lifetimeExtensionRefactor() && mReason != REASON_CLICK) {
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(r, mPkg,
+ packageImportance);
+ }
+
if ((mReason == REASON_LISTENER_CANCEL
&& r.getNotification().isBubbleNotification())
|| (mReason == REASON_CLICK && r.canBubble()
@@ -9329,12 +9342,17 @@ public class NotificationManagerService extends SystemService {
}
}
- protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable) {
- if (Flags.notificationReduceMessagequeueUsage()) {
- sendMessage(Message.obtain(this, cancelRunnable));
+ protected void scheduleCancelNotification(CancelNotificationRunnable cancelRunnable,
+ int delay) {
+ if (lifetimeExtensionRefactor()) {
+ sendMessageDelayed(Message.obtain(this, cancelRunnable), delay);
} else {
- if (!hasCallbacks(cancelRunnable)) {
+ if (Flags.notificationReduceMessagequeueUsage()) {
sendMessage(Message.obtain(this, cancelRunnable));
+ } else {
+ if (!hasCallbacks(cancelRunnable)) {
+ sendMessage(Message.obtain(this, cancelRunnable));
+ }
}
}
}
@@ -9690,7 +9708,7 @@ public class NotificationManagerService extends SystemService {
// remove notification call ends up in not removing the notification.
mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid,
pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank,
- count, listener, SystemClock.elapsedRealtime()));
+ count, listener, SystemClock.elapsedRealtime()), 0);
}
/**
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index afd00af5f818..077ed5a72ae9 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.notification"
+container: "system"
flag {
name: "expire_bitmaps"
@@ -86,3 +87,11 @@ flag {
description: "This flag controls the polite notification attention behavior updates as per UXR feedback"
bug: "270456865"
}
+
+flag {
+ name: "all_notifs_need_ttl"
+ namespace: "systemui"
+ description: "This flag sets a TTL on all notifications that don't already have an app provided one"
+ bug: "331967355"
+}
+
diff --git a/services/core/java/com/android/server/os/Android.bp b/services/core/java/com/android/server/os/Android.bp
index 565dc3b644ea..c5886708ad85 100644
--- a/services/core/java/com/android/server/os/Android.bp
+++ b/services/core/java/com/android/server/os/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "core_os_flags",
package: "com.android.server.os",
+ container: "system",
srcs: [
"*.aconfig",
],
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4eb8b2b980cb..c8fd7e47d80a 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -184,6 +184,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
throwInvalidBugreportFileForCallerException(
bugreportFile, callingInfo.second);
}
+
+ boolean keepBugreportOnRetrieval = false;
+ if (onboardingBugreportV2Enabled()) {
+ keepBugreportOnRetrieval = mBugreportFilesToPersist.contains(
+ bugreportFile);
+ }
+
+ if (!keepBugreportOnRetrieval) {
+ bugreportFilesForUid.remove(bugreportFile);
+ }
} else {
ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
if (bugreportFilesForCaller != null
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index cbc0d54046df..ae33df83e3aa 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.os"
+container: "system"
flag {
name: "proto_tombstone"
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 017cf55541fc..306f77d3d11e 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -25,6 +25,7 @@ import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR
import android.content.Intent;
import android.hardware.usb.UsbManager;
+import android.nfc.NfcAdapter;
import android.provider.AlarmClock;
import android.provider.MediaStore;
@@ -361,6 +362,7 @@ public class DefaultCrossProfileIntentFiltersUtils {
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
+
public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileFilters() {
List<DefaultCrossProfileIntentFilter> filters =
new ArrayList<DefaultCrossProfileIntentFilter>();
@@ -637,6 +639,33 @@ public class DefaultCrossProfileIntentFiltersUtils {
.addDataType("video/*")
.build();
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TAG_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_TAG_DISCOVERED)
+ .build();
+
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TECH_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_TECH_DISCOVERED)
+ .build();
+
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_NDEF_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_NDEF_DISCOVERED)
+ .build();
+
public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
return Arrays.asList(
PARENT_TO_CLONE_SEND_ACTION,
@@ -652,7 +681,10 @@ public class DefaultCrossProfileIntentFiltersUtils {
CLONE_TO_PARENT_SMS_MMS,
CLONE_TO_PARENT_PHOTOPICKER_SELECTION,
CLONE_TO_PARENT_ACTION_PICK_IMAGES,
- CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES
+ CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES,
+ PARENT_TO_CLONE_NFC_TAG_DISCOVERED,
+ PARENT_TO_CLONE_NFC_TECH_DISCOVERED,
+ PARENT_TO_CLONE_NFC_NDEF_DISCOVERED
);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 87b17692fefc..2a3b939c5295 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_INSTALLER;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -2210,6 +2211,7 @@ final class InstallPackageHelper {
Map<String, PackageManager.Property> properties = parsedPackage.getProperties();
if (Flags.aslInApkAppMetadataSource()
&& properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ // ASL file extraction is done in post-install
ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
ps.setAppMetadataSource(APP_METADATA_SOURCE_APK);
} else {
@@ -2809,6 +2811,20 @@ final class InstallPackageHelper {
}
if (succeeded) {
+ if (Flags.aslInApkAppMetadataSource()
+ && pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
+ if (!PackageManagerServiceUtils.extractAppMetadataFromApk(request.getPkg(),
+ pkgSetting.getAppMetadataFilePath())) {
+ synchronized (mPm.mLock) {
+ PackageSetting setting = mPm.mSettings.getPackageLPr(packageName);
+ if (setting != null) {
+ setting.setAppMetadataFilePath(null)
+ .setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
+ }
+ }
+ }
+ }
+
// Clear the uid cache after we installed a new package.
mPm.mPerUidReadTimeoutsCache = null;
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index b87256dbbd9a..712413c49f18 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -60,9 +60,12 @@ public class NoFilteringResolver extends CrossProfileResolver {
public static boolean isIntentRedirectionAllowed(Context context,
AppCloningDeviceConfigHelper appCloningDeviceConfigHelper, boolean resolveForStart,
long flags) {
+ boolean canMatchCloneProfile = (flags & PackageManager.MATCH_CLONE_PROFILE) != 0
+ || (flags & PackageManager.MATCH_CLONE_PROFILE_LONG) != 0;
return isAppCloningBuildingBlocksEnabled(context, appCloningDeviceConfigHelper)
- && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
- && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
+ && (resolveForStart
+ || (canMatchCloneProfile
+ && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
}
public NoFilteringResolver(ComponentResolverApi componentResolver,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 29320aeefde9..a5cd821e319d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -838,7 +838,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
if ((params.installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0
&& !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
- && !Build.IS_DEBUGGABLE) {
+ && !Build.IS_DEBUGGABLE
+ && !PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
// If the bypass flag is set, but not running as system root or shell then remove
// the flag
params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3eeeae7dc260..80a5f3a4c579 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1085,11 +1085,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean isUpdateOwnershipEnforcementEnabled =
mPm.isUpdateOwnershipEnforcementAvailable()
&& existingUpdateOwnerPackageName != null;
+ // For an installation that un-archives an app, if the installer doesn't have the
+ // INSTALL_PACKAGES permission, the user should have already been prompted to confirm the
+ // un-archive request. There's no need for another confirmation during the installation.
+ final boolean isInstallUnarchive =
+ (params.installFlags & PackageManager.INSTALL_UNARCHIVE) != 0;
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
- || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
+ || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall
+ || isInstallUnarchive;
if (noUserActionNecessary) {
return userActionNotTypicallyNeededResponse;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72cd447bb3cd..68cd3e463905 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -19,8 +19,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.admin.flags.Flags.crossUserSuspensionEnabledRo;
-import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
-import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -5232,26 +5230,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return null;
}
File file = new File(filePath);
- if (Flags.aslInApkAppMetadataSource() && !file.exists()
- && ps.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
- AndroidPackageInternal pkg = ps.getPkg();
- if (pkg == null) {
- Slog.w(TAG, "Unable to to extract app metadata for " + packageName
- + ". APK missing from device");
- return null;
- }
- if (!PackageManagerServiceUtils.extractAppMetadataFromApk(pkg, file)) {
- if (file.exists()) {
- file.delete();
- }
- synchronized (mLock) {
- PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- pkgSetting.setAppMetadataFilePath(null);
- pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
- }
- return null;
- }
- }
try {
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 110a29c4ee58..9484d0d7b52b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1570,7 +1570,12 @@ public class PackageManagerServiceUtils {
/**
* Extract the app.metadata file from apk.
*/
- public static boolean extractAppMetadataFromApk(AndroidPackage pkg, File appMetadataFile) {
+ public static boolean extractAppMetadataFromApk(AndroidPackage pkg,
+ String appMetadataFilePath) {
+ if (appMetadataFilePath == null) {
+ return false;
+ }
+ File appMetadataFile = new File(appMetadataFilePath);
Map<String, Property> properties = pkg.getProperties();
if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
return false;
@@ -1596,6 +1601,7 @@ public class PackageManagerServiceUtils {
}
} catch (Exception e) {
Slog.e(TAG, e.getMessage());
+ appMetadataFile.delete();
}
}
return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 034e467d32f8..179379487aba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3387,6 +3387,18 @@ class PackageManagerShellCommand extends ShellCommand {
}
sessionParams.setEnableRollback(true, rollbackStrategy);
break;
+ case "--rollback-impact-level":
+ if (!Flags.recoverabilityDetection()) {
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ int rollbackImpactLevel = Integer.parseInt(peekNextArg());
+ if (rollbackImpactLevel < PackageManager.ROLLBACK_USER_IMPACT_LOW
+ || rollbackImpactLevel
+ > PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+ throw new IllegalArgumentException(
+ rollbackImpactLevel + " is not a valid rollback impact level.");
+ }
+ sessionParams.setRollbackImpactLevel(rollbackImpactLevel);
case "--staged-ready-timeout":
params.stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
break;
@@ -4571,6 +4583,11 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" --full: cause the app to be installed as a non-ephemeral full app");
pw.println(" --enable-rollback: enable rollbacks for the upgrade.");
pw.println(" 0=restore (default), 1=wipe, 2=retain");
+ if (Flags.recoverabilityDetection()) {
+ pw.println(
+ " --rollback-impact-level: set device impact required for rollback.");
+ pw.println(" 0=low (default), 1=high, 2=manual only");
+ }
pw.println(" --install-location: force the install location:");
pw.println(" 0=auto, 1=internal only, 2=prefer external");
pw.println(" --install-reason: indicates why the app is being installed:");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 63386a999d40..e3261bd1322a 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1719,10 +1719,11 @@ public class UserManagerService extends IUserManager.Stub {
}
final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
- if (km != null && km.isDeviceSecure()) {
+ int parentUserId = getProfileParentId(userId);
+ if (km != null && km.isDeviceSecure(parentUserId)) {
showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
- } else if (km != null && !km.isDeviceSecure()
+ } else if (km != null && !km.isDeviceSecure(parentUserId)
&& android.multiuser.Flags.showSetScreenLockDialog()
// TODO(b/330720545): Add a better way to accomplish this, also use it
// to block profile creation w/o device credentials present.
@@ -1732,7 +1733,8 @@ public class UserManagerService extends IUserManager.Stub {
SetScreenLockDialogActivity
.createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
- mContext.startActivity(setScreenLockPromptIntent);
+ mContext.startActivityAsUser(setScreenLockPromptIntent,
+ UserHandle.of(parentUserId));
return false;
} else {
Slog.w(LOG_TAG, "Allowing profile unlock even when device credentials "
@@ -1880,11 +1882,10 @@ public class UserManagerService extends IUserManager.Stub {
&& android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Allow delayed locking since some profile types want to be able to unlock again via
// biometrics.
- ActivityManager.getService()
- .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithDelayedLocking(userId, null);
return;
}
- ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithCallback(userId, null);
}
private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
@@ -6130,7 +6131,7 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
int res;
try {
- res = ActivityManager.getService().stopUser(userId, /* force= */ true,
+ res = ActivityManager.getService().stopUserWithCallback(userId,
new IStopUserCallback.Stub() {
@Override
public void userStopped(int userIdParam) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 324637caa82f..4e02470e087d 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -721,7 +721,8 @@ public class UserRestrictionsUtils {
if (!am.isProfileForeground(UserHandle.of(userId))
&& userId != UserHandle.USER_SYSTEM) {
try {
- ActivityManager.getService().stopUser(userId, false, null);
+ ActivityManager.getService().stopUserExceptCertainProfiles(
+ userId, false, null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7f9c1cfafe68..33b5a707d9df 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -327,7 +327,8 @@ public final class UserTypeFactory {
UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
.setProfileApiVisibility(
UserProperties.PROFILE_API_VISIBILITY_HIDDEN)
- .setItemsRestrictedOnHomeScreen(true));
+ .setItemsRestrictedOnHomeScreen(true)
+ .setUpdateCrossProfileIntentFiltersOnOTA(true));
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
new file mode 100644
index 000000000000..3edd6975ab7d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import static android.os.Process.INVALID_UID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributionFlags;
+import android.app.AppOpsManagerInternal.CheckOpsDelegate;
+import android.app.SyncNotedAppOp;
+import android.content.AttributionSource;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.function.DodecFunction;
+import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.UndecFunction;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerServiceInternal.CheckPermissionDelegate;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface to intercept incoming parameters and outgoing results to permission and appop checks
+ */
+public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDelegate {
+
+ /**
+ * Assigns the package whose permissions are delegated the state of Shell's permissions.
+ *
+ * @param delegateUid the UID whose permissions are delegated to shell
+ * @param packageName the name of the package whose permissions are delegated to shell
+ * @param permissions the set of permissions to delegate to shell. If null then all
+ * permission will be delegated
+ */
+ void setShellPermissionDelegate(int delegateUid, @NonNull String packageName,
+ @Nullable String[] permissions);
+
+ /**
+ * Removes the assigned Shell permission delegate.
+ */
+ void removeShellPermissionDelegate();
+
+ /**
+ * @return a list of permissions delegated to Shell's permission state
+ */
+ @NonNull
+ List<String> getDelegatedPermissionNames();
+
+ /**
+ * @return whether there exists a Shell permission delegate
+ */
+ boolean hasShellPermissionDelegate();
+
+ /**
+ * @param uid the UID to check
+ * @return whether the UID's permissions are delegated to Shell's and the owner of overrides
+ */
+ boolean isDelegateAndOwnerUid(int uid);
+
+ /**
+ * @param uid the UID to check
+ * @param packageName the package to check
+ * @return whether the UID and package combination's permissions are delegated to Shell's
+ * permissions
+ */
+ boolean isDelegatePackage(int uid, @NonNull String packageName);
+
+ /**
+ * Adds permission to be overridden to the given state.
+ *
+ * @param ownerUid the UID of the app who assigned the permission override
+ * @param uid The UID of the app whose permission will be overridden
+ * @param permission The permission whose state will be overridden
+ * @param result The state to override the permission to
+ */
+ void addOverridePermissionState(int ownerUid, int uid, @NonNull String permission,
+ int result);
+
+ /**
+ * Removes overridden permission. UiAutomation must be connected to root user.
+ *
+ * @param uid The UID of the app whose permission is overridden
+ * @param permission The permission whose state will no longer be overridden
+ *
+ * @hide
+ */
+ void removeOverridePermissionState(int uid, @NonNull String permission);
+
+ /**
+ * Clears all overridden permissions for the given UID.
+ *
+ * @param uid The UID of the app whose permissions will no longer be overridden
+ */
+ void clearOverridePermissionStates(int uid);
+
+ /**
+ * Clears all overridden permissions on the device.
+ */
+ void clearAllOverridePermissionStates();
+
+ /**
+ * @return whether there exists any permission overrides
+ */
+ boolean hasOverriddenPermissions();
+
+ /**
+ * @return whether there exists permissions delegated to Shell's permissions or overridden
+ */
+ boolean hasDelegateOrOverrides();
+
+ class AccessCheckDelegateImpl implements AccessCheckDelegate {
+ public static final String SHELL_PKG = "com.android.shell";
+ private int mDelegateAndOwnerUid = INVALID_UID;
+ @Nullable
+ private String mDelegatePackage;
+ @Nullable
+ private String[] mDelegatePermissions;
+ boolean mDelegateAllPermissions;
+ @Nullable
+ private SparseArray<ArrayMap<String, Integer>> mOverridePermissionStates;
+
+ @Override
+ public void setShellPermissionDelegate(int uid, @NonNull String packageName,
+ @Nullable String[] permissions) {
+ mDelegateAndOwnerUid = uid;
+ mDelegatePackage = packageName;
+ mDelegatePermissions = permissions;
+ mDelegateAllPermissions = permissions == null;
+ PackageManager.invalidatePackageInfoCache();
+ }
+
+ @Override
+ public void removeShellPermissionDelegate() {
+ mDelegatePackage = null;
+ mDelegatePermissions = null;
+ mDelegateAllPermissions = false;
+ PackageManager.invalidatePackageInfoCache();
+ }
+
+ @Override
+ public void addOverridePermissionState(int ownerUid, int uid, @NonNull String permission,
+ int state) {
+ if (mOverridePermissionStates == null) {
+ mDelegateAndOwnerUid = ownerUid;
+ mOverridePermissionStates = new SparseArray<>();
+ }
+
+ int uidIdx = mOverridePermissionStates.indexOfKey(uid);
+ ArrayMap<String, Integer> perUidOverrides;
+ if (uidIdx < 0) {
+ perUidOverrides = new ArrayMap<>();
+ mOverridePermissionStates.put(uid, perUidOverrides);
+ } else {
+ perUidOverrides = mOverridePermissionStates.valueAt(uidIdx);
+ }
+
+ perUidOverrides.put(permission, state);
+ PackageManager.invalidatePackageInfoCache();
+ }
+
+ @Override
+ public void removeOverridePermissionState(int uid, @NonNull String permission) {
+ if (mOverridePermissionStates == null) {
+ return;
+ }
+
+ ArrayMap<String, Integer> perUidOverrides = mOverridePermissionStates.get(uid);
+
+ if (perUidOverrides == null) {
+ return;
+ }
+
+ perUidOverrides.remove(permission);
+ PackageManager.invalidatePackageInfoCache();
+
+ if (perUidOverrides.isEmpty()) {
+ mOverridePermissionStates.remove(uid);
+ }
+ if (mOverridePermissionStates.size() == 0) {
+ mOverridePermissionStates = null;
+ }
+ }
+
+ @Override
+ public void clearOverridePermissionStates(int uid) {
+ if (mOverridePermissionStates == null) {
+ return;
+ }
+
+ mOverridePermissionStates.remove(uid);
+ PackageManager.invalidatePackageInfoCache();
+
+ if (mOverridePermissionStates.size() == 0) {
+ mOverridePermissionStates = null;
+ }
+ }
+
+ @Override
+ public void clearAllOverridePermissionStates() {
+ mOverridePermissionStates = null;
+ PackageManager.invalidatePackageInfoCache();
+ }
+
+ @Override
+ public List<String> getDelegatedPermissionNames() {
+ return mDelegatePermissions == null ? null : List.of(mDelegatePermissions);
+ }
+
+ @Override
+ public boolean hasShellPermissionDelegate() {
+ return mDelegateAllPermissions || mDelegatePermissions != null;
+ }
+
+ @Override
+ public boolean isDelegatePackage(int uid, @NonNull String packageName) {
+ return mDelegateAndOwnerUid == uid && TextUtils.equals(mDelegatePackage, packageName);
+ }
+
+ @Override
+ public boolean hasOverriddenPermissions() {
+ return mOverridePermissionStates != null;
+ }
+
+ @Override
+ public boolean isDelegateAndOwnerUid(int uid) {
+ return uid == mDelegateAndOwnerUid;
+ }
+
+ @Override
+ public boolean hasDelegateOrOverrides() {
+ return hasShellPermissionDelegate() || hasOverriddenPermissions();
+ }
+
+ @Override
+ public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
+ @NonNull String persistentDeviceId, @UserIdInt int userId,
+ @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl) {
+ if (TextUtils.equals(mDelegatePackage, packageName) && !SHELL_PKG.equals(packageName)) {
+ if (isDelegatePermission(permissionName)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return checkPermission(SHELL_PKG, permissionName,
+ persistentDeviceId, userId, superImpl);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ if (mOverridePermissionStates != null) {
+ int uid = LocalServices.getService(PackageManagerInternal.class)
+ .getPackageUid(packageName, 0, userId);
+ if (uid >= 0) {
+ Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
+ if (permissionGrants != null && permissionGrants.containsKey(permissionName)) {
+ return permissionGrants.get(permissionName);
+ }
+ }
+ }
+ return superImpl.apply(packageName, permissionName, persistentDeviceId, userId);
+ }
+
+ @Override
+ public int checkUidPermission(int uid, @NonNull String permissionName,
+ @NonNull String persistentDeviceId,
+ @NonNull TriFunction<Integer, String, String, Integer> superImpl) {
+ if (uid == mDelegateAndOwnerUid && uid != Process.SHELL_UID) {
+ if (isDelegatePermission(permissionName)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return checkUidPermission(Process.SHELL_UID, permissionName,
+ persistentDeviceId, superImpl);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ if (mOverridePermissionStates != null) {
+ Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
+ if (permissionGrants != null && permissionGrants.containsKey(permissionName)) {
+ return permissionGrants.get(permissionName);
+ }
+ }
+ return superImpl.apply(uid, permissionName, persistentDeviceId);
+ }
+
+ @Override
+ public int checkOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw,
+ @NonNull HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer>
+ 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, "com.android.shell", null,
+ virtualDeviceId, raw);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(code, uid, packageName, attributionTag, virtualDeviceId, raw);
+ }
+
+ @Override
+ public int checkAudioOperation(int code, int usage, int uid, @Nullable String packageName,
+ @NonNull QuadFunction<Integer, Integer, Integer, String, Integer> 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, usage, shellUid, "com.android.shell");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(code, usage, uid, packageName);
+ }
+
+ @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) {
+ 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, "com.android.shell", featureId,
+ virtualDeviceId, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ }
+
+ @Override
+ public SyncNotedAppOp noteProxyOperation(int code,
+ @NonNull AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation,
+ @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
+ Boolean, SyncNotedAppOp> superImpl) {
+ if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ final int shellUid = UserHandle.getUid(
+ UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return superImpl.apply(code,
+ new AttributionSource(shellUid, Process.INVALID_PID,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getDeviceId(), attributionSource.getNext()),
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skiProxyOperation);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
+ message, shouldCollectMessage, skiProxyOperation);
+ }
+
+ @Override
+ public SyncNotedAppOp startOperation(@NonNull IBinder token, int code, int uid,
+ @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+ @Nullable String message, boolean shouldCollectMessage,
+ @AttributionFlags int attributionFlags, int attributionChainId,
+ @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean,
+ Boolean, String, Boolean, Integer, 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(token, code, shellUid, "com.android.shell",
+ attributionTag, virtualDeviceId, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(token, code, uid, packageName, attributionTag, virtualDeviceId,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ attributionFlags, attributionChainId);
+ }
+
+ @Override
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean skipProxyOperation,
+ @AttributionFlags int proxyAttributionFlags,
+ @AttributionFlags int proxiedAttributionFlags, int attributionChainId,
+ @NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
+ Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
+ SyncNotedAppOp> superImpl) {
+ if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ final int shellUid = UserHandle.getUid(UserHandle.getUserId(
+ attributionSource.getUid()), Process.SHELL_UID);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return superImpl.apply(clientId, code,
+ new AttributionSource(shellUid, Process.INVALID_PID,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getDeviceId(), attributionSource.getNext()),
+ startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ return superImpl.apply(clientId, code, attributionSource, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, skipProxyOperation,
+ proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
+ }
+
+ @Override
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ @NonNull AttributionSource attributionSource, boolean skipProxyOperation,
+ @NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
+ Void> superImpl) {
+ if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ final int shellUid = UserHandle.getUid(UserHandle.getUserId(
+ attributionSource.getUid()), Process.SHELL_UID);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ superImpl.apply(clientId, code,
+ new AttributionSource(shellUid, Process.INVALID_PID,
+ "com.android.shell", attributionSource.getAttributionTag(),
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getDeviceId(), attributionSource.getNext()),
+ skipProxyOperation);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ superImpl.apply(clientId, code, attributionSource, skipProxyOperation);
+ }
+
+ private boolean isDelegatePermission(@NonNull String permission) {
+ // null permissions means all permissions are delegated
+ return mDelegateAndOwnerUid != INVALID_UID
+ && (mDelegateAllPermissions
+ || ArrayUtils.contains(mDelegatePermissions, permission));
+ }
+
+ private boolean isDelegateOp(int code) {
+ if (mDelegateAllPermissions) {
+ return true;
+ }
+ // no permission for the op means the op is targeted
+ final String permission = AppOpsManager.opToPermission(code);
+ if (permission == null) {
+ return true;
+ }
+ return isDelegatePermission(permission);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 21e2bf2e76e5..bd0501d920c4 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -76,11 +76,10 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.permission.PermissionManagerServiceInternal.CheckPermissionDelegate;
import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
@@ -88,7 +87,6 @@ import com.android.server.pm.pkg.PackageState;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -323,6 +321,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return true;
}
+ private void setCheckPermissionDelegateInternal(CheckPermissionDelegate delegate) {
+ synchronized (mLock) {
+ mCheckPermissionDelegate = delegate;
+ }
+ }
+
private boolean checkAutoRevokeAccess(AndroidPackage pkg, int callingUid) {
final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
Manifest.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS)
@@ -369,42 +373,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- private void startShellPermissionIdentityDelegationInternal(int uid,
- @NonNull String packageName, @Nullable List<String> permissionNames) {
- synchronized (mLock) {
- final CheckPermissionDelegate oldDelegate = mCheckPermissionDelegate;
- if (oldDelegate != null && oldDelegate.getDelegatedUid() != uid) {
- throw new SecurityException(
- "Shell can delegate permissions only to one UID at a time");
- }
- final ShellDelegate delegate = new ShellDelegate(uid, packageName, permissionNames);
- setCheckPermissionDelegateLocked(delegate);
- }
- }
-
- private void stopShellPermissionIdentityDelegationInternal() {
- synchronized (mLock) {
- setCheckPermissionDelegateLocked(null);
- }
- }
-
- @Nullable
- private List<String> getDelegatedShellPermissionsInternal() {
- synchronized (mLock) {
- if (mCheckPermissionDelegate == null) {
- return Collections.EMPTY_LIST;
- }
- return mCheckPermissionDelegate.getDelegatedPermissionNames();
- }
- }
-
- private void setCheckPermissionDelegateLocked(@Nullable CheckPermissionDelegate delegate) {
- if (delegate != null || mCheckPermissionDelegate != null) {
- PackageManager.invalidatePackageInfoCache();
- }
- mCheckPermissionDelegate = delegate;
- }
-
@NonNull
private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) {
OneTimePermissionUserManager oneTimePermissionUserManager;
@@ -663,24 +631,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@Override
- public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
- @Nullable List<String> permissionNames) {
- Objects.requireNonNull(packageName, "packageName");
- startShellPermissionIdentityDelegationInternal(uid, packageName, permissionNames);
- }
-
- @Override
- public void stopShellPermissionIdentityDelegation() {
- stopShellPermissionIdentityDelegationInternal();
- }
-
- @Override
- @NonNull
- public List<String> getDelegatedShellPermissions() {
- return getDelegatedShellPermissionsInternal();
- }
-
- @Override
public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) {
mHotwordDetectionServiceProvider = provider;
}
@@ -891,6 +841,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
.getAllPermissionsWithProtectionFlags(protectionFlags);
}
+ @Override
+ public void setCheckPermissionDelegate(CheckPermissionDelegate delegate) {
+ setCheckPermissionDelegateInternal(delegate);
+ }
+
/* End of delegate methods to PermissionManagerServiceInterface */
}
@@ -902,120 +857,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private int[] getAllUserIds() {
return UserManagerService.getInstance().getUserIdsIncludingPreCreated();
}
-
- /**
- * Interface to intercept permission checks and optionally pass through to the original
- * implementation.
- */
- private interface CheckPermissionDelegate {
- /**
- * Get the UID whose permission checks is being delegated.
- *
- * @return the UID
- */
- int getDelegatedUid();
-
- /**
- * Check whether the given package has been granted the specified permission.
- *
- * @param packageName the name of the package to be checked
- * @param permissionName the name of the permission to be checked
- * @param persistentDeviceId The persistent device ID
- * @param userId the user ID
- * @param superImpl the original implementation that can be delegated to
- * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
- * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
- *
- * @see android.content.pm.PackageManager#checkPermission(String, String)
- */
- int checkPermission(@NonNull String packageName, @NonNull String permissionName,
- String persistentDeviceId, @UserIdInt int userId,
- @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl);
-
- /**
- * Check whether the given UID has been granted the specified permission.
- *
- * @param uid the UID to be checked
- * @param permissionName the name of the permission to be checked
- * @param persistentDeviceId The persistent device ID
- * @param superImpl the original implementation that can be delegated to
- * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
- * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
- */
- int checkUidPermission(int uid, @NonNull String permissionName, String persistentDeviceId,
- TriFunction<Integer, String, String, Integer> superImpl);
-
- /**
- * @return list of delegated permissions
- */
- List<String> getDelegatedPermissionNames();
- }
-
- private class ShellDelegate implements CheckPermissionDelegate {
- private final int mDelegatedUid;
- @NonNull
- private final String mDelegatedPackageName;
- @Nullable
- private final List<String> mDelegatedPermissionNames;
-
- public ShellDelegate(int delegatedUid, @NonNull String delegatedPackageName,
- @Nullable List<String> delegatedPermissionNames) {
- mDelegatedUid = delegatedUid;
- mDelegatedPackageName = delegatedPackageName;
- mDelegatedPermissionNames = delegatedPermissionNames;
- }
-
- @Override
- public int getDelegatedUid() {
- return mDelegatedUid;
- }
-
- @Override
- public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
- String persistentDeviceId, int userId,
- @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl) {
- if (mDelegatedPackageName.equals(packageName)
- && isDelegatedPermission(permissionName)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply("com.android.shell", permissionName, persistentDeviceId,
- userId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(packageName, permissionName, persistentDeviceId, userId);
- }
-
- @Override
- public int checkUidPermission(int uid, @NonNull String permissionName,
- String persistentDeviceId,
- @NonNull TriFunction<Integer, String, String, Integer> superImpl) {
- if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return superImpl.apply(Process.SHELL_UID, permissionName, persistentDeviceId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- return superImpl.apply(uid, permissionName, persistentDeviceId);
- }
-
- @Override
- public List<String> getDelegatedPermissionNames() {
- return mDelegatedPermissionNames == null
- ? null
- : new ArrayList<>(mDelegatedPermissionNames);
- }
-
- private boolean isDelegatedPermission(@NonNull String permissionName) {
- // null permissions means all permissions are targeted
- return mDelegatedPermissionNames == null
- || mDelegatedPermissionNames.contains(permissionName);
- }
- }
-
private static final class AttributionSourceRegistry {
private final Object mLock = new Object();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 132cdcee8f8e..a5c12840a645 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -25,6 +25,8 @@ import android.content.pm.PermissionInfo;
import android.permission.PermissionManagerInternal;
import android.util.ArrayMap;
+import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.TriFunction;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
@@ -173,29 +175,47 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter
@PermissionInfo.ProtectionFlags int protectionFlags);
/**
- * Start delegate the permission identity of the shell UID to the given UID.
- *
- * @param uid the UID to delegate shell permission identity to
- * @param packageName the name of the package to delegate shell permission identity to
- * @param permissionNames the names of the permissions to delegate shell permission identity
- * for, or {@code null} for all permissions
+ * Sets the current check permission delegate
*/
- //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- void startShellPermissionIdentityDelegation(int uid,
- @NonNull String packageName, @Nullable List<String> permissionNames);
+ void setCheckPermissionDelegate(CheckPermissionDelegate delegate);
- /**
- * Stop delegating the permission identity of the shell UID.
- *
- * @see #startShellPermissionIdentityDelegation(int, String, List)
+ /**
+ * Interface to intercept permission checks and optionally pass through to the original
+ * implementation.
*/
- //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
- void stopShellPermissionIdentityDelegation();
+ interface CheckPermissionDelegate {
- /**
- * Get all delegated shell permissions.
- */
- @NonNull List<String> getDelegatedShellPermissions();
+ /**
+ * Check whether the given package has been granted the specified permission.
+ *
+ * @param packageName the name of the package to be checked
+ * @param permissionName the name of the permission to be checked
+ * @param persistentDeviceId The persistent device ID
+ * @param userId the user ID
+ * @param superImpl the original implementation that can be delegated to
+ * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+ * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
+ *
+ * @see android.content.pm.PackageManager#checkPermission(String, String)
+ */
+ int checkPermission(@NonNull String packageName, @NonNull String permissionName,
+ @NonNull String persistentDeviceId, @UserIdInt int userId,
+ @NonNull QuadFunction<String, String, String, Integer, Integer> superImpl);
+
+ /**
+ * Check whether the given UID has been granted the specified permission.
+ *
+ * @param uid the UID to be checked
+ * @param permissionName the name of the permission to be checked
+ * @param persistentDeviceId The persistent device ID
+ * @param superImpl the original implementation that can be delegated to
+ * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
+ * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
+ */
+ int checkUidPermission(int uid, @NonNull String permissionName,
+ @NonNull String persistentDeviceId,
+ @NonNull TriFunction<Integer, String, String, Integer> superImpl);
+ }
/**
* Read legacy permissions from legacy permission settings.
diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp
index fa55bf0a30e5..325b6cbf5f2c 100644
--- a/services/core/java/com/android/server/policy/Android.bp
+++ b/services/core/java/com/android/server/policy/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "policy_flags",
package: "com.android.server.policy",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76bf8fd45a43..b4919a4fb9ff 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -174,7 +174,6 @@ import android.service.dreams.IDreamManager;
import android.service.vr.IPersistentVrStateCallbacks;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableBoolean;
@@ -4121,14 +4120,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
IBinder focusedToken) {
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- IBinder targetWindowToken =
- mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
- InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
- event.getDisplayId(), targetWindowToken);
- } else {
- mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
- }
+ IBinder targetWindowToken =
+ mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+ InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+ event.getDisplayId(), targetWindowToken);
}
private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
@@ -5664,6 +5659,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ @Override
+ public void onDisplaySwitchStart(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ mDefaultDisplayPolicy.onDisplaySwitchStart();
+ }
+ }
+
private long getKeyguardDrawnTimeout() {
final boolean bootCompleted =
LocalServices.getService(SystemServiceManager.class).isBootCompleted();
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 5956594acd26..9ca4e273ac39 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -251,12 +251,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
*/
public int getCameraLensCoverState();
- /**
- * Switch the keyboard layout for the given device.
- * Direction should be +1 or -1 to go to the next or previous keyboard layout.
- */
- public void switchKeyboardLayout(int deviceId, int direction);
-
public void shutdown(boolean confirm);
public void reboot(boolean confirm);
public void rebootSafeMode(boolean confirm);
@@ -895,6 +889,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
void onScreenOff();
}
+ /** Called when the physical display starts to switch, e.g. fold/unfold. */
+ void onDisplaySwitchStart(int displayId);
+
/**
* Return whether the default display is on and not blocked by a black surface.
*/
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
index 2154a26acbaf..7914b949ad5f 100644
--- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.policy"
+container: "system"
flag {
name: "support_input_wakeup_delegate"
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 863ff76cb0c7..5d4065df91d6 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "backstage_power_flags",
package: "com.android.server.power.optimization",
+ container: "system",
srcs: [
"stats/*.aconfig",
],
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
index cf23bea97289..dc2d0b320d58 100644
--- a/services/core/java/com/android/server/power/batterysaver/OWNERS
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -1,3 +1,2 @@
-kwekua@google.com
omakoto@google.com
-yamasani@google.com \ No newline at end of file
+yamasani@google.com
diff --git a/services/core/java/com/android/server/power/feature/Android.bp b/services/core/java/com/android/server/power/feature/Android.bp
index 2295b41009de..fee3114015a6 100644
--- a/services/core/java/com/android/server/power/feature/Android.bp
+++ b/services/core/java/com/android/server/power/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "power_flags",
package: "com.android.server.power.feature.flags",
+ container: "system",
srcs: [
"*.aconfig",
],
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index f5dfb5cc3afe..ca58153cf25b 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.power.feature.flags"
+container: "system"
# Important: Flags must be accessed through PowerManagerFlags.
diff --git a/services/core/java/com/android/server/power/hint/Android.bp b/services/core/java/com/android/server/power/hint/Android.bp
index 8a98de673c3d..d7dd902dbabc 100644
--- a/services/core/java/com/android/server/power/hint/Android.bp
+++ b/services/core/java/com/android/server/power/hint/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "power_hint_flags",
package: "com.android.server.power.hint",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index f4afcb141b19..099774420d23 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.power.hint"
+container: "system"
flag {
name: "powerhint_thread_cleanup"
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index c42cceab55be..54ae84f4535b 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.power.optimization"
+container: "system"
flag {
name: "power_monitor_api"
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index f6bcbd057307..a38315c29d53 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -203,6 +203,15 @@ a different `RollbackDataPolicy`, like `ROLLBACK_DATA_POLICY_RETAIN` (1) or
$ adb install --enable-rollback 1 FooV2.apk
```
+### Setting Rollback Impact Level
+
+The `adb install` command accepts the `--rollback-impact-level [0/1/2]` flag to control
+when a rollback can be performed by `PackageWatchdog`.
+
+The default rollback impact level is `ROLLBACK_USER_IMPACT_LOW` (0). To use a
+different impact level, use `ROLLBACK_USER_IMPACT_HIGH` (1) or `ROLLBACK_USER_IMPACT_ONLY_MANUAL`
+(2).
+
### Triggering Rollback Manually
If rollback is available for an application, the pm command can be used to
@@ -225,6 +234,8 @@ $ adb shell dumpsys rollback
469808841:
-state: committed
-timestamp: 2019-04-23T14:57:35.944Z
+ -rollbackLifetimeMillis: 0
+ -rollbackUserImpact: 1
-packages:
com.android.tests.rollback.testapp.B 2 -> 1 [0]
-causePackages:
@@ -232,6 +243,8 @@ $ adb shell dumpsys rollback
649899517:
-state: committed
-timestamp: 2019-04-23T12:55:21.342Z
+ -rollbackLifetimeMillis: 0
+ -rollbackUserImpact: 0
-stagedSessionId: 343374391
-packages:
com.android.tests.rollback.testapex 2 -> 1 [0]
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index d1f91c89a04e..8f39ffb3f53c 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -27,6 +27,7 @@ import android.annotation.WorkerThread;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -960,6 +961,9 @@ class Rollback {
ipw.println("-stateDescription: " + mStateDescription);
ipw.println("-timestamp: " + getTimestamp());
ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
+ if (Flags.recoverabilityDetection()) {
+ ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel());
+ }
ipw.println("-isStaged: " + isStaged());
ipw.println("-originalSessionId: " + getOriginalSessionId());
ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
index e597c3a6b6e6..f7955e86660a 100644
--- a/services/core/java/com/android/server/stats/Android.bp
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "stats_flags",
package: "com.android.server.stats",
+ container: "system",
srcs: [
"stats_flags.aconfig",
],
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 5101a6982fe1..101b98e1785d 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.stats"
+container: "system"
flag {
name: "add_mobile_bytes_transfer_by_proc_state_puller"
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 2b05993c86ba..f62c76e1505a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -61,7 +61,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
import android.service.trust.TrustAgentService;
import android.text.TextUtils;
@@ -155,6 +155,7 @@ public class TrustManagerService extends SystemService {
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
private final LockPatternUtils mLockPatternUtils;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
private FingerprintManager mFingerprintManager;
@@ -252,25 +253,27 @@ public class TrustManagerService extends SystemService {
* cases.
*/
protected static class Injector {
- private final LockPatternUtils mLockPatternUtils;
- private final Looper mLooper;
+ private final Context mContext;
- public Injector(LockPatternUtils lockPatternUtils, Looper looper) {
- mLockPatternUtils = lockPatternUtils;
- mLooper = looper;
+ public Injector(Context context) {
+ mContext = context;
}
LockPatternUtils getLockPatternUtils() {
- return mLockPatternUtils;
+ return new LockPatternUtils(mContext);
+ }
+
+ KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
}
Looper getLooper() {
- return mLooper;
+ return Looper.myLooper();
}
}
public TrustManagerService(Context context) {
- this(context, new Injector(new LockPatternUtils(context), Looper.myLooper()));
+ this(context, new Injector(context));
}
protected TrustManagerService(Context context, Injector injector) {
@@ -280,6 +283,7 @@ public class TrustManagerService extends SystemService {
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mLockPatternUtils = injector.getLockPatternUtils();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
@@ -915,16 +919,16 @@ public class TrustManagerService extends SystemService {
int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
? resolveProfileParent(userId) : userId;
- Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+ mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId),
isWeakUnlockMethodEnabled(authUserId));
} else {
- Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+ mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(userId), false);
}
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
// the password. However, for unlocks with biometric or trust agent, this is required.
- Authorization.onDeviceUnlocked(userId, /* password= */ null);
+ mKeyStoreAuthorization.onDeviceUnlocked(userId, /* password= */ null);
}
}
diff --git a/services/core/java/com/android/server/utils/Android.bp b/services/core/java/com/android/server/utils/Android.bp
index 3a334bee93ff..ffb9aec1cbef 100644
--- a/services/core/java/com/android/server/utils/Android.bp
+++ b/services/core/java/com/android/server/utils/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "com.android.server.utils-aconfig",
package: "com.android.server.utils",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 163116b9c5c7..6f37837d4f3b 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.utils"
+container: "system"
flag {
name: "anr_timer_service"
diff --git a/services/core/java/com/android/server/utils/quota/OWNERS b/services/core/java/com/android/server/utils/quota/OWNERS
index a2943f39db24..469acb23f0b9 100644
--- a/services/core/java/com/android/server/utils/quota/OWNERS
+++ b/services/core/java/com/android/server/utils/quota/OWNERS
@@ -1,4 +1,3 @@
dplotnikov@google.com
-kwekua@google.com
omakoto@google.com
yamasani@google.com
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 6fc9d9a8ce1d..ad54efcd11d3 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -32,8 +32,9 @@ import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -42,10 +43,11 @@ import java.util.concurrent.atomic.AtomicInteger;
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final SimpleDateFormat DEBUG_TIME_FORMAT =
- new SimpleDateFormat("HH:mm:ss.SSS");
- private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
- new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+ private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -240,10 +242,12 @@ abstract class Vibration {
@Override
public String toString() {
- return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
- + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
- + ", endTime: "
- + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
+ return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mCreateTime))
+ + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mStartTime))
+ + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mEndTime)))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", playedEffect: " + mPlayedEffect
@@ -267,12 +271,14 @@ abstract class Vibration {
boolean isExternalVibration = mPlayedEffect == null;
String timingsStr = String.format(Locale.ROOT,
"%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
- DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
isExternalVibration ? "external" : "effect",
mStatus.name().toLowerCase(Locale.ROOT),
mDurationMs,
- mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
- mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+ mStartTime == 0 ? ""
+ : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
+ mEndTime == 0 ? ""
+ : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
String paramStr = String.format(Locale.ROOT,
" | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
@@ -307,10 +313,12 @@ abstract class Vibration {
pw.increaseIndent();
pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
pw.println("durationMs = " + mDurationMs);
- pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
- pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
- pw.println("endTime = "
- + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+ pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mCreateTime)));
+ pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mStartTime)));
+ pw.println("endTime = " + (mEndTime == 0 ? null
+ : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
pw.println("playedEffect = " + mPlayedEffect);
pw.println("originalEffect = " + mOriginalEffect);
pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 10317c997581..b33fa6f56a23 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -51,8 +51,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@@ -66,8 +67,8 @@ final class VibratorControlService extends IVibratorControlService.Stub {
private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
private static final int NO_SCALE = -1;
- private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
- new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
private final VibrationParamsRecords mVibrationParamsRecords;
private final VibratorControllerHolder mVibratorControllerHolder;
@@ -567,7 +568,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
public void dump(IndentingPrintWriter pw) {
String line = String.format(Locale.ROOT,
"%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
- DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
mOperation.name().toLowerCase(Locale.ROOT),
(mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
Long.toBinaryString(mTypesMask), createVibrationUsagesString());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 5175b74116f4..6905802809c3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -86,7 +87,7 @@ public class WallpaperCropper {
public interface WallpaperCropUtils {
/**
- * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+ * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
*/
Rect getCrop(Point displaySize, Point bitmapSize,
SparseArray<Rect> suggestedCrops, boolean rtl);
@@ -120,16 +121,23 @@ public class WallpaperCropper {
public Rect getCrop(Point displaySize, Point bitmapSize,
SparseArray<Rect> suggestedCrops, boolean rtl) {
- // Case 1: if no crops are provided, center align the full image
+ int orientation = getOrientation(displaySize);
+
+ // Case 1: if no crops are provided, show the full image (from the left, or right if RTL).
if (suggestedCrops == null || suggestedCrops.size() == 0) {
- Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
- float scale = Math.min(
- ((float) bitmapSize.x) / displaySize.x,
- ((float) bitmapSize.y) / displaySize.y);
- crop.scale(scale);
- crop.offset((bitmapSize.x - crop.width()) / 2,
- (bitmapSize.y - crop.height()) / 2);
- return crop;
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ // The first exception is if the device is a foldable and we're on the folded screen.
+ // In that case, show the center of what's on the unfolded screen.
+ int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+ // Let the system know that we're showing the full image on the unfolded screen
+ SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
+ newSuggestedCrops.put(unfoldedOrientation, crop);
+ // This will fall into "Case 4" of this function and center the folded screen
+ return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
+ }
+ return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
}
// If any suggested crop is invalid, fallback to case 1
@@ -142,8 +150,6 @@ public class WallpaperCropper {
}
}
- int orientation = getOrientation(displaySize);
-
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
@@ -168,11 +174,21 @@ public class WallpaperCropper {
suggestedCrop = suggestedCrops.get(unfoldedOrientation);
suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
if (suggestedCrop != null) {
- // only keep the visible part (without parallax)
+ // compute the visible part (without parallax) of the unfolded screen
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
- return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ // compute the folded crop, at the center of the crop of the unfolded screen
+ Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ // if we removed some width, add it back to add a parallax effect
+ if (res.width() < adjustedCrop.width()) {
+ if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
+ else res.right = Math.max(res.right, adjustedCrop.right);
+ // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
+ res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
+ }
+ return res;
}
+
// Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
// have the suggested crop of the relative folded orientation, reuse it by adding content.
int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
@@ -274,11 +290,8 @@ public class WallpaperCropper {
if (additionalWidthForParallax > MAX_PARALLAX) {
int widthToRemove = (int) Math.ceil(
(additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
- if (rtl) {
- adjustedCrop.left += widthToRemove;
- } else {
- adjustedCrop.right -= widthToRemove;
- }
+ adjustedCrop.left += widthToRemove / 2;
+ adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2;
}
} else {
// TODO (b/281648899) the third case is not always correct, fix that.
@@ -366,6 +379,24 @@ public class WallpaperCropper {
*/
SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
+ // If the suggested crops is single-element map with (ORIENTATION_UNKNOWN, cropHint),
+ // Crop the bitmap using the cropHint and compute the crops for cropped bitmap.
+ Rect cropHint = suggestedCrops.get(ORIENTATION_UNKNOWN);
+ if (cropHint != null) {
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ if (suggestedCrops.size() != 1 || !bitmapRect.contains(cropHint)) {
+ Slog.w(TAG, "Couldn't get default crops from suggested crops " + suggestedCrops
+ + " for bitmap of size " + bitmapSize + "; ignoring suggested crops");
+ return getDefaultCrops(new SparseArray<>(), bitmapSize);
+ }
+ Point cropSize = new Point(cropHint.width(), cropHint.height());
+ SparseArray<Rect> relativeDefaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+ for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+ relativeDefaultCrops.valueAt(i).offset(cropHint.left, cropHint.top);
+ }
+ return relativeDefaultCrops;
+ }
+
SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
== View.LAYOUT_DIRECTION_RTL;
@@ -422,26 +453,74 @@ public class WallpaperCropper {
} else {
boolean needCrop = false;
boolean needScale;
- boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
Point bitmapSize = new Point(options.outWidth, options.outHeight);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ if (multiCrop()) {
+ // Check that the suggested crops per screen orientation are all within the bitmap.
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ int orientation = wallpaper.mCropHints.keyAt(i);
+ Rect crop = wallpaper.mCropHints.valueAt(i);
+ if (crop.isEmpty() || !bitmapRect.contains(crop)) {
+ Slog.w(TAG, "Invalid crop " + crop + " for orientation " + orientation
+ + " and bitmap size " + bitmapSize + "; clearing suggested crops.");
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(bitmapRect);
+ break;
+ }
+ }
+ }
final Rect cropHint;
- if (multiCrop) {
- SparseArray<Rect> defaultDisplayCrops =
- getDefaultCrops(wallpaper.mCropHints, bitmapSize);
- // adapt the entries in wallpaper.mCropHints for the actual display
+
+ // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
+ // a wallpaper with cropHints = null and cropHint = rect.
+ Rect tempCropHint = wallpaper.mCropHints.get(ORIENTATION_UNKNOWN);
+ if (multiCrop() && tempCropHint != null) {
+ wallpaper.cropHint.set(tempCropHint);
+ wallpaper.mCropHints.clear();
+ }
+ if (multiCrop() && wallpaper.mCropHints.size() > 0) {
+ // Some suggested crops per screen orientation were provided,
+ // use them to compute the default crops for this device
+ SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+ // Adapt the provided crops to match the actual crops for the default display
SparseArray<Rect> updatedCropHints = new SparseArray<>();
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
int orientation = wallpaper.mCropHints.keyAt(i);
- Rect defaultCrop = defaultDisplayCrops.get(orientation);
+ Rect defaultCrop = defaultCrops.get(orientation);
if (defaultCrop != null) {
updatedCropHints.put(orientation, defaultCrop);
}
}
wallpaper.mCropHints = updatedCropHints;
- cropHint = getTotalCrop(defaultDisplayCrops);
+
+ // Finally, compute the cropHint based on the default crops
+ cropHint = getTotalCrop(defaultCrops);
wallpaper.cropHint.set(cropHint);
+ if (DEBUG) {
+ Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops
+ + " based on suggested crops: " + wallpaper.mCropHints);
+ }
+ } else if (multiCrop()) {
+ // No crops per screen orientation were provided, but an overall cropHint may be
+ // defined in wallpaper.cropHint. Compute the default crops for the sub-image
+ // defined by the cropHint, then recompute the cropHint based on the default crops.
+ // If the cropHint is empty or invalid, ignore it and use the full image.
+ if (wallpaper.cropHint.isEmpty()) wallpaper.cropHint.set(bitmapRect);
+ if (!bitmapRect.contains(wallpaper.cropHint)) {
+ Slog.w(TAG, "Ignoring wallpaper.cropHint = " + wallpaper.cropHint
+ + "; not within the bitmap of size " + bitmapSize);
+ wallpaper.cropHint.set(bitmapRect);
+ }
+ Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+ SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+ cropHint = getTotalCrop(defaultCrops);
+ cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
+ wallpaper.cropHint.set(cropHint);
+ if (DEBUG) {
+ Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops);
+ }
} else {
cropHint = new Rect(wallpaper.cropHint);
}
@@ -455,11 +534,11 @@ public class WallpaperCropper {
}
// Empty crop means use the full image
- if (cropHint.isEmpty()) {
+ if (!multiCrop() && cropHint.isEmpty()) {
cropHint.left = cropHint.top = 0;
cropHint.right = options.outWidth;
cropHint.bottom = options.outHeight;
- } else {
+ } else if (!multiCrop()) {
// force the crop rect to lie within the measured bounds
int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0;
int dy = cropHint.bottom > options.outHeight
@@ -473,19 +552,19 @@ public class WallpaperCropper {
if (cropHint.top < 0) {
cropHint.top = 0;
}
-
- // Don't bother cropping if what we're left with is identity
- needCrop = (options.outHeight > cropHint.height()
- || options.outWidth > cropHint.width());
}
+ // Don't bother cropping if what we're left with is identity
+ needCrop = (options.outHeight > cropHint.height()
+ || options.outWidth > cropHint.width());
+
// scale if the crop height winds up not matching the recommended metrics
needScale = cropHint.height() > wpData.mHeight
|| cropHint.height() > GLHelper.getMaxTextureSize()
|| cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
- if (needScale && !multiCrop) {
+ if (needScale && !multiCrop()) {
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -543,7 +622,7 @@ public class WallpaperCropper {
final Rect estimateCrop = new Rect(cropHint);
estimateCrop.scale(1f / options.inSampleSize);
float hRatio = (float) wpData.mHeight / estimateCrop.height();
- if (multiCrop) {
+ if (multiCrop()) {
// make sure the crop height is at most the display largest dimension
hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
/ estimateCrop.height();
@@ -557,7 +636,7 @@ public class WallpaperCropper {
if (DEBUG) {
Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
}
- if (multiCrop) {
+ if (multiCrop()) {
// clear custom crop guidelines, fallback to system default
wallpaper.mCropHints.clear();
generateCropInternal(wallpaper);
@@ -618,7 +697,7 @@ public class WallpaperCropper {
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
safeWidth, safeHeight, true);
- if (multiCrop) {
+ if (multiCrop()) {
wallpaper.mSampleSize =
((float) cropHint.height()) / finalCrop.getHeight();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 02594d2d8d22..b792f7909fc8 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -172,11 +172,6 @@ class WallpaperData {
SparseArray<Rect> mCropHints = new SparseArray<>();
/**
- * cropHints will be ignored if this flag is false
- */
- boolean mSupportsMultiCrop;
-
- /**
* The phone orientation when the wallpaper was set. Only relevant for image wallpapers
*/
int mOrientationWhenSet = ORIENTATION_UNKNOWN;
@@ -204,7 +199,6 @@ class WallpaperData {
if (source.mCropHints != null) {
this.mCropHints = source.mCropHints.clone();
}
- this.mSupportsMultiCrop = source.mSupportsMultiCrop;
this.allowBackup = source.allowBackup;
this.primaryColors = source.primaryColors;
this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 7f53ea372687..4aefb54889aa 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -324,10 +324,7 @@ public class WallpaperDataParser {
getAttributeInt(parser, "totalCropTop", 0),
getAttributeInt(parser, "totalCropRight", 0),
getAttributeInt(parser, "totalCropBottom", 0));
- wallpaper.mSupportsMultiCrop = multiCrop() && (
- parser.getAttributeBoolean(null, "supportsMultiCrop", false)
- || mImageWallpaper.equals(wallpaper.wallpaperComponent));
- if (wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) {
wallpaper.mCropHints = new SparseArray<>();
for (Pair<Integer, String> pair: screenDimensionPairs()) {
Rect cropHint = new Rect(
@@ -342,16 +339,14 @@ public class WallpaperDataParser {
}
if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- int orientation = legacyCropHint.width() < legacyCropHint.height()
- ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
if (!legacyCropHint.isEmpty()) {
- wallpaper.mCropHints.put(orientation, legacyCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
- } else {
+ } else if (!multiCrop()) {
wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
@@ -467,13 +462,12 @@ public class WallpaperDataParser {
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
- out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
-
- if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
if (wallpaper.mCropHints == null) {
Slog.e(TAG, "cropHints should not be null when saved");
wallpaper.mCropHints = new SparseArray<>();
}
+ Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint);
for (Pair<Integer, String> pair : screenDimensionPairs()) {
Rect cropHint = wallpaper.mCropHints.get(pair.first);
if (cropHint == null) continue;
@@ -493,12 +487,14 @@ public class WallpaperDataParser {
}
}
if (pair.first == orientationToPutInLegacyCrop) {
- out.attributeInt(null, "cropLeft", cropHint.left);
- out.attributeInt(null, "cropTop", cropHint.top);
- out.attributeInt(null, "cropRight", cropHint.right);
- out.attributeInt(null, "cropBottom", cropHint.bottom);
+ rectToPutInLegacyCrop.set(cropHint);
}
}
+ out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left);
+ out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top);
+ out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right);
+ out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom);
+
out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 885baf65013f..802f196adf3b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@ import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
import static com.android.window.flags.Flags.multiCrop;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -380,7 +381,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
// Outside of the lock since it will synchronize itself
- notifyWallpaperColorsChanged(wallpaper);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wallpaper);
}
@Override
@@ -403,12 +404,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) {
+ notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (DEBUG) {
Slog.i(TAG, "Notifying wallpaper colors changed");
}
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector ->
- notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId));
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId, which));
}
}
@@ -425,6 +430,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
int displayId) {
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, displayId, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
+ int displayId, int which) {
boolean needsExtraction;
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
@@ -449,8 +459,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
notify = extractColors(wallpaper);
}
if (notify) {
- notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper),
- wallpaper.mWhich, wallpaper.userId, displayId);
+ notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+ wallpaper.userId, displayId);
}
}
@@ -504,6 +514,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* @return true unless the wallpaper changed during the color computation
*/
private boolean extractColors(WallpaperData wallpaper) {
+ if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
String cropFile = null;
boolean defaultImageWallpaper = false;
int wallpaperId;
@@ -803,7 +814,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
- if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
mWindowManagerInternal.setWallpaperCropHints(mToken,
mWallpaperCropper.getRelativeCropHints(wallpaper));
} else {
@@ -1148,10 +1159,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
// internally by this class.
- if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
+ boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+ if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
return;
}
mWallpaper.primaryColors = primaryColors;
+ // only save the colors for ImageWallpaper - for live wallpapers, the colors
+ // are always recomputed after a reboot.
+ if (offloadColorExtraction() && isImageWallpaper) {
+ saveSettingsLocked(mWallpaper.userId);
+ }
}
notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId);
}
@@ -1177,7 +1194,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
// This will trigger onComputeColors in the wallpaper engine.
// It's fine to be locked in here since the binder is oneway.
- connector.mEngine.requestWallpaperColors();
+ if (!offloadColorExtraction() || mWallpaper.primaryColors == null) {
+ connector.mEngine.requestWallpaperColors();
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
@@ -1811,6 +1830,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Offload color extraction to another thread since switchUser will be called
// from the main thread.
FgThread.getHandler().post(() -> {
+ if (offloadColorExtraction()) return;
notifyWallpaperColorsChanged(systemWallpaper);
if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper);
notifyWallpaperColorsChanged(mFallbackWallpaper);
@@ -1917,11 +1937,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final ComponentName component;
final int finalWhich;
- if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
- clearWallpaperBitmaps(lockWallpaper);
- }
- if ((which & FLAG_SYSTEM) > 0) {
- clearWallpaperBitmaps(wallpaper);
+ // Clear any previous ImageWallpaper related fields
+ List<WallpaperData> toClear = new ArrayList<>();
+ if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
+ if ((which & FLAG_SYSTEM) > 0) toClear.add(wallpaper);
+ for (WallpaperData wallpaperToClear : toClear) {
+ clearWallpaperBitmaps(wallpaperToClear);
+ if (multiCrop()) {
+ wallpaperToClear.mCropHints.clear();
+ wallpaperToClear.cropHint.set(0, 0, 0, 0);
+ wallpaperToClear.mSampleSize = 1;
+ }
}
// lock only case: set the system wallpaper component to both screens
@@ -2225,7 +2251,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
checkPermission(READ_WALLPAPER_INTERNAL);
WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
: mWallpaperMap.get(userId);
- if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+ if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ return null;
+ }
SparseArray<Rect> relativeSuggestedCrops =
mWallpaperCropper.getRelativeCropHints(wallpaper);
Point croppedBitmapSize = new Point(
@@ -2255,7 +2283,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
int[] screenOrientations, List<Rect> crops) {
- SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
List<Rect> result = new ArrayList<>();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
@@ -2272,7 +2300,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new UnsupportedOperationException(
"This method should only be called with the multi crop flag enabled");
}
- SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
return WallpaperCropper.getTotalCrop(defaultCrops);
}
@@ -2714,8 +2742,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
});
// Need to extract colors again to re-calculate dark hints after
// applying dimming.
- wp.mIsColorExtractedFromDim = true;
- pendingColorExtraction.add(wp);
+ if (!offloadColorExtraction()) {
+ wp.mIsColorExtractedFromDim = true;
+ pendingColorExtraction.add(wp);
+ }
changed = true;
}
}
@@ -2724,7 +2754,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
for (WallpaperData wp: pendingColorExtraction) {
- notifyWallpaperColorsChanged(wp);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wp);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2860,10 +2890,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return null;
}
- int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
- SparseArray<Rect> cropMap = !multiCrop() ? null
- : getCropMap(screenOrientations, crops, currentOrientation);
- Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+ SparseArray<Rect> cropMap = !multiCrop() ? null : getCropMap(screenOrientations, crops);
+ Rect cropHint = multiCrop() || crops == null || crops.isEmpty() ? new Rect() : crops.get(0);
final boolean fromForegroundApp = !multiCrop() ? false
: isFromForegroundApp(callingPackage);
@@ -2912,12 +2940,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper.setComplete = completion;
wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
: isFromForegroundApp(callingPackage);
- if (!multiCrop()) wallpaper.cropHint.set(cropHint);
- if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
- if (multiCrop()) wallpaper.mCropHints = cropMap;
+ wallpaper.cropHint.set(cropHint);
+ if (multiCrop()) {
+ wallpaper.mCropHints = cropMap;
+ wallpaper.mSampleSize = 1f;
+ wallpaper.mOrientationWhenSet =
+ mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+ }
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
- wallpaper.mOrientationWhenSet = currentOrientation;
+ if (offloadColorExtraction()) wallpaper.primaryColors = null;
}
return pfd;
} finally {
@@ -2926,16 +2958,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
- int currentOrientation) {
+ private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops) {
if ((crops == null ^ screenOrientations == null)
|| (crops != null && crops.size() != screenOrientations.length)) {
throw new IllegalArgumentException(
"Illegal crops/orientations lists: must both be null, or both the same size");
}
SparseArray<Rect> cropMap = new SparseArray<>();
- boolean unknown = false;
- if (crops != null && crops.size() != 0) {
+ if (crops != null && !crops.isEmpty()) {
for (int i = 0; i < crops.size(); i++) {
Rect crop = crops.get(i);
int width = crop.width(), height = crop.height();
@@ -2943,22 +2973,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
}
int orientation = screenOrientations[i];
- if (orientation == ORIENTATION_UNKNOWN) {
- if (currentOrientation == ORIENTATION_UNKNOWN) {
- throw new IllegalArgumentException(
- "Invalid orientation: " + ORIENTATION_UNKNOWN);
- }
- unknown = true;
- orientation = currentOrientation;
+ if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+ throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+ + "screen orientation should only be used in a singleton map");
}
cropMap.put(orientation, crop);
}
}
- if (unknown && cropMap.size() > 1) {
- throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
- + "orientation should only be used in a singleton map (in which case it"
- + "represents the current orientation of the default display)");
- }
return cropMap;
}
@@ -2975,7 +2996,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
lockWP.wallpaperId = sysWP.wallpaperId;
lockWP.cropHint.set(sysWP.cropHint);
- lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
if (sysWP.mCropHints != null) {
lockWP.mCropHints = sysWP.mCropHints.clone();
}
@@ -3072,6 +3092,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
boolean shouldNotifyColors = false;
+
+ // If the lockscreen wallpaper is set to the same as the home screen, notify that the
+ // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
+ boolean shouldNotifyLockscreenColors = false;
boolean bindSuccess;
final WallpaperData newWallpaper;
@@ -3096,7 +3120,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
- newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
newWallpaper.imageWallpaperPending = false;
newWallpaper.mWhich = which;
newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3118,7 +3141,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
bindSuccess = bindWallpaperComponentLocked(name, /* force */
forceRebind, /* fromUser */ true, newWallpaper, reply);
if (bindSuccess) {
- if (!same) {
+ if (!same || (offloadColorExtraction() && forceRebind)) {
newWallpaper.primaryColors = null;
} else {
if (newWallpaper.connection != null) {
@@ -3142,6 +3165,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
shouldNotifyColors = true;
+ if (offloadColorExtraction()) {
+ shouldNotifyColors = false;
+ shouldNotifyLockscreenColors = !force && same && !systemIsBoth
+ && which == (FLAG_SYSTEM | FLAG_LOCK);
+ }
if (which == (FLAG_SYSTEM | FLAG_LOCK)) {
if (DEBUG) {
@@ -3170,6 +3198,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (shouldNotifyColors) {
notifyWallpaperColorsChanged(newWallpaper);
}
+ if (shouldNotifyLockscreenColors) {
+ notifyWallpaperColorsChanged(newWallpaper, FLAG_LOCK);
+ }
+
return bindSuccess;
}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index e230b95fe907..6776f268e743 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -20,6 +20,7 @@ import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
import android.app.wearable.Flags;
+import android.app.wearable.IWearableSensingCallback;
import android.app.wearable.WearableSensingManager;
import android.content.ComponentName;
import android.content.Context;
@@ -75,10 +76,14 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
* Provides a secure connection to the wearable.
*
* @param secureWearableConnection The secure connection to the wearable
- * @param callback The callback for service status
+ * @param wearableSensingCallback The callback for requests such as openFile from the
+ * WearableSensingService.
+ * @param statusCallback The callback for service status
*/
public void provideSecureConnection(
- ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ ParcelFileDescriptor secureWearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
if (DEBUG) {
Slog.i(TAG, "#provideSecureConnection");
}
@@ -87,7 +92,8 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
TAG,
"FLAG_ENABLE_RESTART_WSS_PROCESS is disabled. Do not attempt to restart the"
+ " WearableSensingService process");
- provideSecureConnectionInternal(secureWearableConnection, callback);
+ provideSecureConnectionInternal(
+ secureWearableConnection, wearableSensingCallback, statusCallback);
return;
}
synchronized (mSecureConnectionLock) {
@@ -105,30 +111,37 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
WearableSensingManager.STATUS_CHANNEL_ERROR);
}
mNextSecureConnectionContext =
- new SecureWearableConnectionContext(secureWearableConnection, callback);
+ new SecureWearableConnectionContext(
+ secureWearableConnection, wearableSensingCallback, statusCallback);
return;
}
if (!mSecureConnectionProvided) {
// no need to kill the process
- provideSecureConnectionInternal(secureWearableConnection, callback);
+ provideSecureConnectionInternal(
+ secureWearableConnection, wearableSensingCallback, statusCallback);
mSecureConnectionProvided = true;
return;
}
mNextSecureConnectionContext =
- new SecureWearableConnectionContext(secureWearableConnection, callback);
+ new SecureWearableConnectionContext(
+ secureWearableConnection, wearableSensingCallback, statusCallback);
// Killing the process causes the binder to die. #binderDied will then be triggered
killWearableSensingServiceProcess();
}
}
private void provideSecureConnectionInternal(
- ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+ ParcelFileDescriptor secureWearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
Slog.d(TAG, "Providing secure wearable connection.");
var unused =
post(
service -> {
service.provideSecureConnection(
- secureWearableConnection, callback);
+ secureWearableConnection,
+ wearableSensingCallback,
+ statusCallback);
try {
// close the local fd after it has been sent to the WSS process
secureWearableConnection.close();
@@ -146,6 +159,7 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
// This will call #post, which will recreate the process and bind to it
provideSecureConnectionInternal(
mNextSecureConnectionContext.mSecureConnection,
+ mNextSecureConnectionContext.mWearableSensingCallback,
mNextSecureConnectionContext.mStatusCallback);
mNextSecureConnectionContext = null;
} else {
@@ -164,23 +178,29 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
* Provides the implementation a data stream to the wearable.
*
* @param parcelFileDescriptor The data stream to the wearable
+ * @param wearableSensingCallback The callback for requests such as openFile from the
+ * WearableSensingService.
* @param callback The callback for service status
*/
- public void provideDataStream(ParcelFileDescriptor parcelFileDescriptor,
+ public void provideDataStream(
+ ParcelFileDescriptor parcelFileDescriptor,
+ IWearableSensingCallback wearableSensingCallback,
RemoteCallback callback) {
if (DEBUG) {
Slog.i(TAG, "Providing data stream.");
}
- var unused = post(
- service -> {
- service.provideDataStream(parcelFileDescriptor, callback);
- try {
- // close the local fd after it has been sent to the WSS process
- parcelFileDescriptor.close();
- } catch (IOException ex) {
- Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
- }
- });
+ var unused =
+ post(
+ service -> {
+ service.provideDataStream(
+ parcelFileDescriptor, wearableSensingCallback, callback);
+ try {
+ // close the local fd after it has been sent to the WSS process
+ parcelFileDescriptor.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+ }
+ });
}
/**
@@ -308,12 +328,16 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
private static class SecureWearableConnectionContext {
final ParcelFileDescriptor mSecureConnection;
+ final IWearableSensingCallback mWearableSensingCallback;
final RemoteCallback mStatusCallback;
SecureWearableConnectionContext(
- ParcelFileDescriptor secureWearableConnection, RemoteCallback statusCallback) {
- this.mSecureConnection = secureWearableConnection;
- this.mStatusCallback = statusCallback;
+ ParcelFileDescriptor secureWearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
+ mSecureConnection = secureWearableConnection;
+ mWearableSensingCallback = wearableSensingCallback;
+ mStatusCallback = statusCallback;
}
}
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 34b9fe968994..eb170b702eb9 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -25,10 +25,12 @@ import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.wearable.Flags;
+import android.app.wearable.IWearableSensingCallback;
import android.app.wearable.WearableSensingManager;
import android.companion.CompanionDeviceManager;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
@@ -46,6 +48,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -61,12 +64,17 @@ final class WearableSensingManagerPerUserService extends
WearableSensingManagerService> {
private static final String TAG = WearableSensingManagerPerUserService.class.getSimpleName();
+ private final PackageManagerInternal mPackageManagerInternal;
+
@Nullable
@VisibleForTesting
RemoteWearableSensingService mRemoteService;
@Nullable private VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;
+
+ @GuardedBy("mLock")
private ComponentName mComponentName;
+
private final Object mSecureChannelLock = new Object();
@GuardedBy("mSecureChannelLock")
@@ -75,6 +83,7 @@ final class WearableSensingManagerPerUserService extends
WearableSensingManagerPerUserService(
@NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
super(master, lock, userId);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
public static void notifyStatusCallback(RemoteCallback statusCallback, int statusCode) {
@@ -190,14 +199,19 @@ final class WearableSensingManagerPerUserService extends
* service.
*/
public void onProvideConnection(
- ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ ParcelFileDescriptor wearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
Slog.i(TAG, "onProvideConnection in per user service.");
+ final IWearableSensingCallback wrappedWearableSensingCallback;
synchronized (mLock) {
if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment.");
- notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
+ wrappedWearableSensingCallback = wrapWearableSensingCallback(wearableSensingCallback);
}
synchronized (mSecureChannelLock) {
if (mSecureChannel != null) {
@@ -218,7 +232,9 @@ final class WearableSensingManagerPerUserService extends
synchronized (mLock) {
ensureRemoteServiceInitiated();
mRemoteService.provideSecureConnection(
- secureTransport, callback);
+ secureTransport,
+ wrappedWearableSensingCallback,
+ statusCallback);
}
}
@@ -237,7 +253,7 @@ final class WearableSensingManagerPerUserService extends
}
if (Flags.enableProvideWearableConnectionApi()) {
notifyStatusCallback(
- callback,
+ statusCallback,
WearableSensingManager.STATUS_CHANNEL_ERROR);
}
}
@@ -246,7 +262,8 @@ final class WearableSensingManagerPerUserService extends
} catch (IOException ex) {
Slog.e(TAG, "Unable to create the secure channel.", ex);
if (Flags.enableProvideWearableConnectionApi()) {
- notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_CHANNEL_ERROR);
}
}
}
@@ -257,17 +274,22 @@ final class WearableSensingManagerPerUserService extends
*/
public void onProvideDataStream(
ParcelFileDescriptor parcelFileDescriptor,
- RemoteCallback callback) {
+ @Nullable IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
Slog.i(TAG, "onProvideDataStream in per user service.");
synchronized (mLock) {
if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment.");
- notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
Slog.i(TAG, "calling over to remote servvice.");
ensureRemoteServiceInitiated();
- mRemoteService.provideDataStream(parcelFileDescriptor, callback);
+ mRemoteService.provideDataStream(
+ parcelFileDescriptor,
+ wrapWearableSensingCallback(wearableSensingCallback),
+ statusCallback);
}
}
@@ -456,4 +478,37 @@ final class WearableSensingManagerPerUserService extends
}
};
}
+
+ @GuardedBy("mLock")
+ private @Nullable IWearableSensingCallback wrapWearableSensingCallback(
+ IWearableSensingCallback callbackFromAppProcess) {
+ if (callbackFromAppProcess == null) {
+ return null;
+ }
+ if (mComponentName == null) {
+ Slog.w(TAG, "Cannot create WearableSensingCallback because mComponentName is null.");
+ return null;
+ }
+ if (Binder.getCallingUid()
+ != mPackageManagerInternal.getPackageUid(
+ mComponentName.getPackageName(), /* flags= */ 0, mUserId)) {
+ Slog.d(
+ TAG,
+ "Caller does not belong to the package that provides the WearableSensingService"
+ + " implementation. Do not forward WearableSensingCallback to"
+ + " WearableSensingService.");
+ return null;
+ }
+ return new IWearableSensingCallback.Stub() {
+ @Override
+ public void openFile(
+ String filename,
+ AndroidFuture<ParcelFileDescriptor> futureFromWearableSensingService)
+ throws RemoteException {
+ // TODO(b/331395522): Intercept the PFD received from the app process and verify it
+ // is read-only
+ callbackFromAppProcess.openFile(filename, futureFromWearableSensingService);
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 8742ab1dd95b..110100ac735b 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -20,11 +20,13 @@ import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.IWearableSensingCallback;
import android.app.wearable.IWearableSensingManager;
import android.app.wearable.WearableSensingDataRequest;
import android.app.wearable.WearableSensingManager;
@@ -213,21 +215,37 @@ public class WearableSensingManagerService extends
return null;
}
+ /**
+ * Provides a data stream to the WearableSensingService.
+ *
+ * <p>This method is only called from adb command via {@link WearableSensingShellCommand}.
+ */
@VisibleForTesting
- void provideDataStream(@UserIdInt int userId, ParcelFileDescriptor parcelFileDescriptor,
+ void provideDataStream(
+ @UserIdInt int userId,
+ ParcelFileDescriptor parcelFileDescriptor,
RemoteCallback callback) {
synchronized (mLock) {
final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
if (mService != null) {
- mService.onProvideDataStream(parcelFileDescriptor, callback);
+ mService.onProvideDataStream(
+ parcelFileDescriptor, /* wearableSensingCallback= */ null, callback);
} else {
Slog.w(TAG, "Service not available.");
}
}
}
+ /**
+ * Provides data to the WearableSensingService.
+ *
+ * <p>This method is only called from adb command via {@link WearableSensingShellCommand}.
+ */
@VisibleForTesting
- void provideData(@UserIdInt int userId, PersistableBundle data, SharedMemory sharedMemory,
+ void provideData(
+ @UserIdInt int userId,
+ PersistableBundle data,
+ SharedMemory sharedMemory,
RemoteCallback callback) {
synchronized (mLock) {
final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
@@ -400,40 +418,48 @@ public class WearableSensingManagerService extends
@Override
public void provideConnection(
- ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+ ParcelFileDescriptor wearableConnection,
+ IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
Slog.i(TAG, "WearableSensingManagerInternal provideConnection.");
Objects.requireNonNull(wearableConnection);
- Objects.requireNonNull(callback);
+ Objects.requireNonNull(statusCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available.");
WearableSensingManagerPerUserService.notifyStatusCallback(
- callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
callPerUserServiceIfExist(
- service -> service.onProvideConnection(wearableConnection, callback),
- callback);
+ service ->
+ service.onProvideConnection(
+ wearableConnection, wearableSensingCallback, statusCallback),
+ statusCallback);
}
@Override
public void provideDataStream(
- ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
+ ParcelFileDescriptor parcelFileDescriptor,
+ @Nullable IWearableSensingCallback wearableSensingCallback,
+ RemoteCallback statusCallback) {
Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
Objects.requireNonNull(parcelFileDescriptor);
- Objects.requireNonNull(callback);
+ Objects.requireNonNull(statusCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available.");
- WearableSensingManagerPerUserService.notifyStatusCallback(callback,
- WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ WearableSensingManagerPerUserService.notifyStatusCallback(
+ statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
callPerUserServiceIfExist(
- service -> service.onProvideDataStream(parcelFileDescriptor, callback),
- callback);
+ service ->
+ service.onProvideDataStream(
+ parcelFileDescriptor, wearableSensingCallback, statusCallback),
+ statusCallback);
}
@Override
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index c6e8eb8d3743..5e345965dc80 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -310,8 +310,10 @@ public class SystemImpl implements SystemInterface {
ArrayList<String> apksToPin = new ArrayList<>();
boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true);
- for (String sharedLib : appInfo.sharedLibraryFiles) {
- apksToPin.add(sharedLib);
+ if (appInfo.sharedLibraryFiles != null) {
+ for (String sharedLib : appInfo.sharedLibraryFiles) {
+ apksToPin.add(sharedLib);
+ }
}
apksToPin.add(appInfo.sourceDir);
if (!pinSharedFirst) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6f16d2cb41fa..2b43326f4c51 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CO
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
@@ -93,6 +94,8 @@ import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
+import android.view.WindowManager.TransitionFlags;
+import android.view.WindowManager.TransitionType;
import android.view.WindowManagerPolicyConstants;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -357,14 +360,15 @@ final class AccessibilityController {
// Not relevant for the window observer.
}
- void onWMTransition(int displayId, @WindowManager.TransitionType int type) {
+ void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
- FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type);
+ mAccessibilityTracing.logTrace(TAG + ".onWMTransition",
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "displayId=" + displayId + "; type=" + type + "; flags=" + flags);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.onWMTransition(displayId, type);
+ displayMagnifier.onWMTransition(displayId, type, flags);
}
// Not relevant for the window observer.
}
@@ -574,6 +578,11 @@ final class AccessibilityController {
void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
if (lastTarget != null) {
mFocusedWindow.remove(lastTarget.getDisplayId());
+ final DisplayMagnifier displayMagnifier =
+ mDisplayMagnifiers.get(lastTarget.getDisplayId());
+ if (displayMagnifier != null) {
+ displayMagnifier.onFocusLost(lastTarget);
+ }
}
if (newTarget != null) {
int displayId = newTarget.getDisplayId();
@@ -625,6 +634,7 @@ final class AccessibilityController {
private final AccessibilityControllerInternalImpl mAccessibilityTracing;
private final MagnificationCallbacks mCallbacks;
+ private final UserContextChangedNotifier mUserContextChangedNotifier;
private final long mLongAnimationDuration;
@@ -653,6 +663,7 @@ final class AccessibilityController {
mDisplayContent = displayContent;
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
+ mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler);
mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
? null : new MagnifiedViewport();
mAccessibilityTracing =
@@ -764,40 +775,43 @@ final class AccessibilityController {
+ " displayId: " + displayId);
}
final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
- if (isMagnifierActivated) {
- switch (transition) {
- case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_TO_FRONT:
- case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN:
- case WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE:
- case WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
- }
+ if (!isMagnifierActivated) {
+ return;
+ }
+ switch (transition) {
+ case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
+ case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ case WindowManager.TRANSIT_OLD_TASK_OPEN:
+ case WindowManager.TRANSIT_OLD_TASK_TO_FRONT:
+ case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN:
+ case WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE:
+ case WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
+ mUserContextChangedNotifier.onAppWindowTransition(transition);
}
}
}
- void onWMTransition(int displayId, @WindowManager.TransitionType int type) {
+ void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".onWMTransition",
- FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; type=" + type);
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "displayId=" + displayId + "; type=" + type + "; flags=" + flags);
}
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
+ " displayId: " + displayId);
}
final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
- if (isMagnifierActivated) {
- // All opening/closing situations.
- switch (type) {
- case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
- case WindowManager.TRANSIT_CLOSE:
- case WindowManager.TRANSIT_TO_BACK:
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
- }
+ if (!isMagnifierActivated) {
+ return;
+ }
+ // All opening/closing situations.
+ switch (type) {
+ case WindowManager.TRANSIT_OPEN:
+ case WindowManager.TRANSIT_TO_FRONT:
+ case WindowManager.TRANSIT_CLOSE:
+ case WindowManager.TRANSIT_TO_BACK:
+ mUserContextChangedNotifier.onWMTransition(type, flags);
}
}
@@ -813,13 +827,14 @@ final class AccessibilityController {
+ " displayId: " + windowState.getDisplayId());
}
final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
+ if (!isMagnifierActivated || !windowState.shouldMagnify()) {
+ return;
+ }
+ mUserContextChangedNotifier.onWindowTransition(windowState, transition);
final int type = windowState.mAttrs.type;
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
case WindowManagerPolicy.TRANSIT_SHOW: {
- if (!isMagnifierActivated || !windowState.shouldMagnify()) {
- break;
- }
switch (type) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION:
@@ -859,6 +874,14 @@ final class AccessibilityController {
}
}
+ void onFocusLost(InputTarget target) {
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
+ if (!isMagnifierActivated) {
+ return;
+ }
+ mUserContextChangedNotifier.onFocusLost(target);
+ }
+
void getMagnifiedFrameInContentCoords(Rect rect) {
mMagnificationRegion.getBounds(rect);
rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
@@ -1584,6 +1607,65 @@ final class AccessibilityController {
}
}
}
+
+ private class UserContextChangedNotifier {
+
+ private final Handler mHandler;
+
+ private boolean mHasDelayedNotificationForRecentsToFrontTransition;
+
+ UserContextChangedNotifier(Handler handler) {
+ mHandler = handler;
+ }
+
+ void onAppWindowTransition(int transition) {
+ sendUserContextChangedNotification();
+ }
+
+ // For b/324949652, if the onWMTransition callback is triggered when the finger down
+ // event on navigation bar to bring the recents window to front, we'll delay the
+ // notifying of the context changed, then send it if there is a following onFocusChanged
+ // callback triggered. Before the onFocusChanged, if there are some other transitions
+ // causing the notifying, or the recents/home window is removed, then we won't need the
+ // delayed notification anymore.
+ void onWMTransition(@TransitionType int type, @TransitionFlags int flags) {
+ if (Flags.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition()
+ && type == WindowManager.TRANSIT_TO_FRONT
+ && (flags & TRANSIT_FLAG_IS_RECENTS) != 0) {
+ // Delay the recents to front transition notification then send after if needed.
+ mHasDelayedNotificationForRecentsToFrontTransition = true;
+ } else {
+ sendUserContextChangedNotification();
+ }
+ }
+
+ void onWindowTransition(WindowState windowState, int transition) {
+ // If there is a delayed notification for recents to front transition but the
+ // home/recents window has been removed from screen, the delayed notification is not
+ // needed anymore.
+ if (transition == WindowManagerPolicy.TRANSIT_EXIT
+ && windowState.isActivityTypeHomeOrRecents()
+ && mHasDelayedNotificationForRecentsToFrontTransition) {
+ mHasDelayedNotificationForRecentsToFrontTransition = false;
+ }
+ }
+
+ void onFocusLost(InputTarget target) {
+ // If there is a delayed notification for recents to front transition and
+ // onFocusLost is triggered, we assume that the users leave current window to
+ // the home/recents window, thus we'll need to send the delayed notification.
+ if (mHasDelayedNotificationForRecentsToFrontTransition) {
+ sendUserContextChangedNotification();
+ }
+ }
+
+ private void sendUserContextChangedNotification() {
+ // Since the context changed will be notified, the delayed notification is
+ // not needed anymore.
+ mHasDelayedNotificationForRecentsToFrontTransition = false;
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+ }
+ }
}
static boolean isUntouchableNavigationBar(WindowState windowState,
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5184e49385b2..c2b9128daac5 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -96,7 +96,6 @@ import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationStartListener;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
@@ -105,7 +104,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -117,7 +115,6 @@ import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Pair;
import android.util.Slog;
@@ -137,6 +134,7 @@ import android.view.animation.TranslateAnimation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.DumpUtils.Dump;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -244,7 +242,7 @@ public class AppTransition implements Dump {
mHandler = new Handler(service.mH.getLooper());
mDisplayContent = displayContent;
mTransitionAnimation = new TransitionAnimation(
- context, ProtoLog.isEnabled(WM_DEBUG_ANIM), TAG);
+ context, ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG), TAG);
final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
com.android.internal.R.styleable.Window);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f6681c571090..f7b4a6748411 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -779,6 +780,10 @@ class BackNavigationController {
&& wc.asTaskFragment() == null) {
continue;
}
+ // Only care if visibility changed.
+ if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) {
+ continue;
+ }
// WC can be visible due to setLaunchBehind
if (wc.isVisibleRequested()) {
mTmpOpenApps.add(wc);
@@ -843,14 +848,14 @@ class BackNavigationController {
* @param targets The final animation targets derived in transition.
* @param finishedTransition The finished transition target.
*/
- boolean onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
+ void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
if (finishedTransition == mWaitTransitionFinish) {
clearBackAnimations();
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
- return false;
+ return;
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Handling the deferred animation after transition finished");
@@ -878,7 +883,7 @@ class BackNavigationController {
+ " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
cancelPendingAnimation();
- return false;
+ return;
}
// Ensure the final animation targets which hidden by transition could be visible.
@@ -887,9 +892,14 @@ class BackNavigationController {
wc.prepareSurfaces();
}
- scheduleAnimation(mPendingAnimationBuilder);
- mPendingAnimationBuilder = null;
- return true;
+ // The pending builder could be cleared due to prepareSurfaces
+ // => updateNonSystemOverlayWindowsVisibilityIfNeeded
+ // => setForceHideNonSystemOverlayWindowIfNeeded
+ // => updateFocusedWindowLocked => onFocusWindowChanged.
+ if (mPendingAnimationBuilder != null) {
+ scheduleAnimation(mPendingAnimationBuilder);
+ mPendingAnimationBuilder = null;
+ }
}
private void cancelPendingAnimation() {
@@ -1552,15 +1562,17 @@ class BackNavigationController {
return this;
}
+ // WC must be Activity/TaskFragment/Task
boolean containTarget(@NonNull WindowContainer wc) {
if (mOpenTargets != null) {
for (int i = mOpenTargets.length - 1; i >= 0; --i) {
- if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+ if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)
+ || wc.hasChild(mOpenTargets[i])) {
return true;
}
}
}
- return wc == mCloseTarget || mCloseTarget.hasChild(wc);
+ return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
/**
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 47f4a66995af..0e446b8eaf8c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -38,9 +38,11 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
+import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
+import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -539,6 +541,16 @@ public class BackgroundActivityStartController {
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
}
+ // features
+ sb.append("; balImproveRealCallerVisibilityCheck: ")
+ .append(balImproveRealCallerVisibilityCheck());
+ sb.append("; balRequireOptInByPendingIntentCreator: ")
+ .append(balRequireOptInByPendingIntentCreator());
+ sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
+ sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
+ .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
+ sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
+ .append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index a29cb60ff545..ca5f26aa4cc8 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -26,6 +26,10 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.os.Message;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Slog;
import android.view.DisplayInfo;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
@@ -35,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
+import com.android.window.flags.Flags;
import java.util.Arrays;
import java.util.Objects;
@@ -65,6 +70,12 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
WM_OVERRIDE_FIELDS.setFields(out, override);
};
+ private static final String TAG = "DeferredDisplayUpdater";
+
+ private static final String TRACE_TAG_WAIT_FOR_TRANSITION =
+ "Screen unblock: wait for transition";
+ private static final int WAIT_FOR_TRANSITION_TIMEOUT = 1000;
+
private final DisplayContent mDisplayContent;
@NonNull
@@ -88,6 +99,18 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
@NonNull
private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
+ /** Whether {@link #mScreenUnblocker} should wait for transition to be ready. */
+ private boolean mShouldWaitForTransitionWhenScreenOn;
+
+ /** The message to notify PhoneWindowManager#finishWindowsDrawn. */
+ @Nullable
+ private Message mScreenUnblocker;
+
+ private final Runnable mScreenUnblockTimeoutRunnable = () -> {
+ Slog.e(TAG, "Timeout waiting for the display switch transition to start");
+ continueScreenUnblocking();
+ };
+
public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
mDisplayContent = displayContent;
mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
@@ -248,6 +271,7 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
getCurrentDisplayChange(fromRotation, startBounds);
displayChange.setPhysicalDisplayChanged(true);
+ transition.addTransactionCompletedListener(this::continueScreenUnblocking);
mDisplayContent.mTransitionController.requestStartTransition(transition,
/* startTask= */ null, /* remoteTransition= */ null, displayChange);
@@ -277,6 +301,58 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
return !Objects.equals(first.uniqueId, second.uniqueId);
}
+ @Override
+ public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
+ DisplayAreaInfo newDisplayAreaInfo) {
+ // Unblock immediately in case there is no transition. This is unlikely to happen.
+ if (mScreenUnblocker != null && !mDisplayContent.mTransitionController.inTransition()) {
+ mScreenUnblocker.sendToTarget();
+ mScreenUnblocker = null;
+ }
+ }
+
+ @Override
+ public void onDisplaySwitching(boolean switching) {
+ mShouldWaitForTransitionWhenScreenOn = switching;
+ }
+
+ @Override
+ public boolean waitForTransition(@NonNull Message screenUnblocker) {
+ if (!Flags.waitForTransitionOnDisplaySwitch()) return false;
+ if (!mShouldWaitForTransitionWhenScreenOn) {
+ return false;
+ }
+ mScreenUnblocker = screenUnblocker;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, screenUnblocker.hashCode());
+ }
+
+ mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
+ mDisplayContent.mWmService.mH.postDelayed(mScreenUnblockTimeoutRunnable,
+ WAIT_FOR_TRANSITION_TIMEOUT);
+ return true;
+ }
+
+ /**
+ * Continues the screen unblocking flow, could be called either on a binder thread as
+ * a result of surface transaction completed listener or from {@link WindowManagerService#mH}
+ * handler in case of timeout
+ */
+ private void continueScreenUnblocking() {
+ synchronized (mDisplayContent.mWmService.mGlobalLock) {
+ mShouldWaitForTransitionWhenScreenOn = false;
+ mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
+ if (mScreenUnblocker == null) {
+ return;
+ }
+ mScreenUnblocker.sendToTarget();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, mScreenUnblocker.hashCode());
+ }
+ mScreenUnblocker = null;
+ }
+ }
+
/**
* Diff result: fields are the same
*/
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fe280cbcc205..cde3e68e43c9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -470,7 +470,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final DisplayRotation mDisplayRotation;
@Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
DisplayFrames mDisplayFrames;
- private final DisplayUpdater mDisplayUpdater;
+ final DisplayUpdater mDisplayUpdater;
private boolean mInTouchMode;
@@ -2705,7 +2705,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Returns true if the specified UID has access to this display.
*/
boolean hasAccess(int uid) {
- return mDisplay.hasAccess(uid);
+ int userId = UserHandle.getUserId(uid);
+ boolean isUserVisibleOnDisplay = mWmService.mUmInternal.isUserVisible(
+ userId, mDisplayId);
+ return mDisplay.hasAccess(uid)
+ && (userId == UserHandle.USER_SYSTEM || isUserVisibleOnDisplay);
}
boolean isPrivate() {
@@ -4562,7 +4566,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- void attachAndShow(Transaction t) {
+ /**
+ * Attaches the snapshot of IME (a snapshot will be taken if there wasn't one) to the IME
+ * target task and shows it. If the given {@param anyTargetTask} is true, the snapshot won't
+ * be skipped by the activity type of IME target task.
+ */
+ void attachAndShow(Transaction t, boolean anyTargetTask) {
final DisplayContent dc = mImeTarget.getDisplayContent();
// Prepare IME screenshot for the target if it allows to attach into.
final Task task = mImeTarget.getTask();
@@ -4570,7 +4579,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final boolean renewImeSurface = mImeSurface == null
|| mImeSurface.getWidth() != dc.mInputMethodWindow.getFrame().width()
|| mImeSurface.getHeight() != dc.mInputMethodWindow.getFrame().height();
- if (task != null && !task.isActivityTypeHomeOrRecents()) {
+ // The exclusion of home/recents is an optimization for regular task switch because
+ // home/recents won't appear in recents task.
+ if (task != null && (anyTargetTask || !task.isActivityTypeHomeOrRecents())) {
ScreenCapture.ScreenshotHardwareBuffer imeBuffer = renewImeSurface
? dc.mWmService.mTaskSnapshotController.snapshotImeFromAttachedTask(task)
: null;
@@ -4638,7 +4649,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
removeImeSurfaceImmediately();
mImeScreenshot = new ImeScreenshot(
mWmService.mSurfaceControlFactory.apply(null), imeTarget);
- mImeScreenshot.attachAndShow(t);
+ // If the caller requests to hide IME, then allow to show IME snapshot for any target task.
+ // So IME won't look like suddenly disappeared. It usually happens when turning off screen.
+ mImeScreenshot.attachAndShow(t, hideImeWindow /* anyTargetTask */);
if (mInputMethodWindow != null && hideImeWindow) {
// Hide the IME window when deciding to show IME snapshot on demand.
// InsetsController will make IME visible again before animating it.
@@ -6997,7 +7010,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// by finishing the recents animation and moving it to top. That also avoids flickering
// due to wait for previous activity to be paused if it supports PiP that ignores the
// effect of resume-while-pausing.
- if (r == null || r == mAnimatingRecents) {
+ if (r == null || r == mAnimatingRecents || r.getDisplayId() != mDisplayId) {
return;
}
if (mAnimatingRecents != null && mRecentsWillBeTop) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 16f7373ebc5e..a5037ea0ff07 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -779,6 +779,11 @@ public class DisplayPolicy {
return mLidState;
}
+ private void onDisplaySwitchFinished() {
+ mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(false);
+ }
+
public void setAwake(boolean awake) {
synchronized (mLock) {
if (awake == mAwake) {
@@ -797,7 +802,7 @@ public class DisplayPolicy {
mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
if (!awake) {
- mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ onDisplaySwitchFinished();
}
}
}
@@ -866,7 +871,7 @@ public class DisplayPolicy {
/** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */
public void screenTurnedOn() {
- mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ onDisplaySwitchFinished();
}
public void screenTurnedOff() {
@@ -2187,6 +2192,11 @@ public class DisplayPolicy {
mDisplayContent.mTransitionController.getCollectingTransitionId();
}
+ /** If this is called, expect that there will be an onDisplayChanged about unique id. */
+ public void onDisplaySwitchStart() {
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(true);
+ }
+
@NavigationBarPosition
int navigationBarPosition(int displayRotation) {
if (mNavigationBar != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java
index e611177210e8..918b180ab1cb 100644
--- a/services/core/java/com/android/server/wm/DisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DisplayUpdater.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.NonNull;
+import android.os.Message;
import android.view.Surface;
import android.window.DisplayAreaInfo;
@@ -49,4 +50,16 @@ interface DisplayUpdater {
@Surface.Rotation int previousRotation, @Surface.Rotation int newRotation,
@NonNull DisplayAreaInfo newDisplayAreaInfo) {
}
+
+ /**
+ * Called with {@code true} when physical display is going to switch. And {@code false} when
+ * the display is turned on or the device goes to sleep.
+ */
+ default void onDisplaySwitching(boolean switching) {
+ }
+
+ /** Returns {@code true} if the transition will control when to turn on the screen. */
+ default boolean waitForTransition(@NonNull Message screenUnBlocker) {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b8bb258aa2ce..0ad601de95ec 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -61,6 +61,7 @@ import android.view.WindowManager;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -225,13 +226,16 @@ class KeyguardController {
if (keyguardShowing) {
state.mDismissalRequested = false;
}
- if (goingAwayRemoved) {
- // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
- // before holding the sleep token again.
+ if (goingAwayRemoved || (keyguardShowing && Flags.keyguardAppearTransition())) {
+ // Keyguard decided to show or stopped going away. Send a transition to animate back
+ // to the locked state before holding the sleep token again
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- mWindowManager.executeAppTransition();
+ if (Flags.keyguardAppearTransition()) {
+ dc.mWallpaperController.adjustWallpaperWindows();
+ }
+ dc.executeAppTransition();
}
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 3ef6eeb2ecf3..63bbb3140bfb 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -44,6 +44,7 @@ import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FastPrintWriter;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -209,7 +210,7 @@ class RemoteAnimationController implements DeathRecipient {
Slog.e(TAG, "Failed to start remote animation", e);
onAnimationFinished();
}
- if (ProtoLog.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS, LogLevel.DEBUG)) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
writeStartDebugStatement();
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index d67684c7038e..c632714bd8ce 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,6 +32,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
@@ -192,7 +193,7 @@ public class SurfaceAnimator {
return;
}
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
mAnimation.dump(pw, "");
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 319e2b024f2f..1b380aadee35 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -112,6 +112,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
/**
@@ -233,6 +234,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
*/
private ArrayList<Task> mTransientHideTasks;
+ @VisibleForTesting
+ ArrayList<Runnable> mTransactionCompletedListeners = null;
+
/** Custom activity-level animation options and callbacks. */
private TransitionInfo.AnimationOptions mOverrideOptions;
private IRemoteCallback mClientAnimationStartCallback = null;
@@ -1640,6 +1644,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
commitVisibleActivities(transaction);
commitVisibleWallpapers();
+ if (mTransactionCompletedListeners != null) {
+ for (int i = 0; i < mTransactionCompletedListeners.size(); i++) {
+ final Runnable listener = mTransactionCompletedListeners.get(i);
+ transaction.addTransactionCompletedListener(Runnable::run,
+ (stats) -> listener.run());
+ }
+ }
+
// Fall-back to the default display if there isn't one participating.
final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
: mController.mAtm.mRootWindowContainer.getDefaultDisplay();
@@ -1814,7 +1826,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final AccessibilityController accessibilityController =
dc.mWmService.mAccessibilityController;
if (accessibilityController.hasCallbacks()) {
- accessibilityController.onWMTransition(dc.getDisplayId(), mType);
+ accessibilityController.onWMTransition(dc.getDisplayId(), mType, mFlags);
}
}
} else {
@@ -1862,6 +1874,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
/**
+ * Adds a listener that will be executed after the start transaction of this transition
+ * is presented on the screen, the listener will be executed on a binder thread
+ */
+ void addTransactionCompletedListener(Runnable listener) {
+ if (mTransactionCompletedListeners == null) {
+ mTransactionCompletedListeners = new ArrayList<>();
+ }
+ mTransactionCompletedListeners.add(listener);
+ }
+
+ /**
* Checks if the transition contains order changes.
*
* This is a shallow check that doesn't account for collection in parallel, unlike
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a2f6fb4c08ad..59bda54eb089 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -22,7 +22,6 @@ import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
@@ -858,10 +857,6 @@ class WallpaperController {
}
public void updateWallpaperTokens(boolean keyguardLocked) {
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget);
- }
updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
keyguardLocked);
}
@@ -870,6 +865,8 @@ class WallpaperController {
* Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
*/
private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "updateWallpaperTokens requestedVisibility=%b on"
+ + " keyguardLocked=%b", visibility, keyguardLocked);
WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
WallpaperWindowToken topWallpaperToken =
topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 55eeaf22cca8..5c24eee63317 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -274,6 +274,12 @@ class WallpaperWindowToken extends WindowToken {
}
@Override
+ boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+ // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync.
+ return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
+ }
+
+ @Override
public String toString() {
if (stringName == null) {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 80889d1ac972..90ac57613cff 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -113,6 +113,7 @@ import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -3405,7 +3406,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
a.restrictDuration(MAX_APP_TRANSITION_DURATION);
}
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6762e7a29b0b..8a68afbd501f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -49,6 +49,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
+import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -3427,7 +3428,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
throw new SecurityException("Requires CONTROL_KEYGUARD permission");
}
- if (mAtmService.mKeyguardController.isShowingDream()) {
+ if (!dreamHandlesConfirmKeys() && mAtmService.mKeyguardController.isShowingDream()) {
mAtmService.mTaskSupervisor.wakeUp("leaveDream");
}
synchronized (mGlobalLock) {
@@ -3684,12 +3685,6 @@ public class WindowManagerService extends IWindowManager.Stub
// Called by window manager policy. Not exposed externally.
@Override
- public void switchKeyboardLayout(int deviceId, int direction) {
- mInputManager.switchKeyboardLayout(deviceId, direction);
- }
-
- // Called by window manager policy. Not exposed externally.
- @Override
public void shutdown(boolean confirm) {
// Pass in the UI context, since ShutdownThread requires it (to show UI).
ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
@@ -8076,6 +8071,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
boolean allWindowsDrawn = false;
synchronized (mGlobalLock) {
+ if (mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
+ // Use the ready-to-play of transition as the signal.
+ return;
+ }
container.waitForAllWindowsDrawn();
mWindowPlacerLocked.requestTraversal();
mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
@@ -10163,10 +10162,12 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO(b/323580163): Check if already shown and update shown state.
if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(),
w.getOwningUid(), w.getWindowToken())) {
- Toast.makeText(mContext, Looper.getMainLooper(),
- mContext.getString(R.string.screen_not_shared_sensitive_content),
- Toast.LENGTH_SHORT)
- .show();
+ mH.post(() -> {
+ Toast.makeText(mContext, Looper.getMainLooper(),
+ mContext.getString(R.string.screen_not_shared_sensitive_content),
+ Toast.LENGTH_SHORT)
+ .show();
+ });
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6ac2774941e1..aac567c2e455 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -202,6 +202,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
private volatile boolean mHasImeService;
+ /**
+ * Whether this process can use realtime prioirity (SCHED_FIFO) for its UI and render threads
+ * when this process is SCHED_GROUP_TOP_APP.
+ */
+ private final boolean mUseFifoUiScheduling;
+
/** Whether {@link #mActivities} is not empty. */
private volatile boolean mHasActivities;
/** All activities running in the process (exclude destroying). */
@@ -340,6 +346,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
mIsActivityConfigOverrideAllowed = false;
}
+ mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
+ && (isSysUiPackage || mAtm.isCallerRecents(uid));
mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
&& mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
@@ -1901,6 +1909,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
+ /** Returns {@code true} if the process prefers to use fifo scheduling. */
+ public boolean useFifoUiScheduling() {
+ return mUseFifoUiScheduling;
+ }
+
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void onTopProcChanged() {
if (mAtm.mVrController.isInterestingToSchedGroup()) {
@@ -2078,6 +2091,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
pw.println();
}
+ if (mUseFifoUiScheduling) {
+ pw.println(prefix + " mUseFifoUiScheduling=true");
+ }
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b716dc67f1a3..7a0245bc1acc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -247,6 +247,7 @@ import android.window.OnBackInvokedCallbackInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ToBooleanFunction;
@@ -4739,7 +4740,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
void onExitAnimationDone() {
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.VERBOSE)) {
final AnimationAdapter animationAdapter = mSurfaceAnimator.getAnimation();
StringWriter sw = new StringWriter();
if (animationAdapter != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a242d4242388..6fd7aa0e4a78 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -61,6 +61,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
@@ -586,7 +587,7 @@ class WindowStateAnimator {
mWin.mAttrs, attr, TRANSIT_OLD_NONE);
}
}
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.VERBOSE)) {
ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s"
+ " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index 82f9aadba9f4..d24afabe95a4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -92,7 +92,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG
&& parser.getName().equals(TAG_VALUE)) {
- values.add(parser.nextText().trim());
+ values.add(parser.nextText());
count--;
}
}
@@ -111,7 +111,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
restrictions.putParcelableArray(key,
bundleList.toArray(new Bundle[bundleList.size()]));
} else {
- String value = parser.nextText().trim();
+ String value = parser.nextText();
if (ATTR_TYPE_BOOLEAN.equals(valType)) {
restrictions.putBoolean(key, Boolean.parseBoolean(value));
} else if (ATTR_TYPE_INTEGER.equals(valType)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 28fe5dfc9bc1..f39d0193f28a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1640,6 +1640,10 @@ final class DevicePolicyEngine {
mAdminPolicySize.get(admin.getUserId()).get(admin) - sizeOf(
policyState.getPoliciesSetByAdmins().get(admin)));
}
+ if (!mAdminPolicySize.contains(admin.getUserId())
+ || !mAdminPolicySize.get(admin.getUserId()).containsKey(admin)) {
+ return;
+ }
if (mAdminPolicySize.get(admin.getUserId()).get(admin) <= 0) {
mAdminPolicySize.get(admin.getUserId()).remove(admin);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b34092ca280f..1dd719eb02aa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11509,10 +11509,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setApplicationRestrictions(ComponentName who, String callerPackage,
- String packageName, Bundle restrictions) {
+ String packageName, Bundle restrictions, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
+ // This check is eventually made in UMS, checking here to fail early.
+ String validationResult =
+ FrameworkParsingPackageUtils.validateName(packageName, false, false);
+ if (validationResult != null) {
+ throw new IllegalArgumentException("Invalid package name: " + validationResult);
+ }
+
if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
@@ -11520,12 +11527,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getPackageName(),
caller.getUserId()
);
- // This check is eventually made in UMS, checking here to fail early.
- String validationResult =
- FrameworkParsingPackageUtils.validateName(packageName, false, false);
- if (validationResult != null) {
- throw new IllegalArgumentException("Invalid package name: " + validationResult);
- }
if (restrictions == null || restrictions.isEmpty()) {
mDevicePolicyEngine.removeLocalPolicy(
@@ -11541,6 +11542,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
setBackwardsCompatibleAppRestrictions(
caller, packageName, restrictions, caller.getUserHandle());
+ } else if (Flags.dmrhCanSetAppRestriction()) {
+ final boolean isRoleHolder;
+ if (who != null) {
+ // DO or PO
+ Preconditions.checkCallAuthorization(
+ (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ Preconditions.checkCallAuthorization(!parent,
+ "DO or PO cannot call this on parent");
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Delegates, or the DMRH. Only DMRH can call this on COPE parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ // DMRH caller uses policy engine, others still use legacy code path
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ if (restrictions == null || restrictions.isEmpty()) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ enforcingAdmin,
+ affectedUserId);
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ enforcingAdmin,
+ new BundlePolicyValue(restrictions),
+ affectedUserId);
+ }
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
+ } else {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ mUserManager.setApplicationRestrictions(packageName, restrictions,
+ caller.getUserHandle());
+ });
+ }
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
@@ -12823,7 +12875,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.i(LOG_TAG, "Stopping user %d", userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
+ switch (mInjector.getIActivityManager().stopUserWithCallback(userId, null)) {
case ActivityManager.USER_OP_SUCCESS:
return UserManager.USER_OPERATION_SUCCESS;
case ActivityManager.USER_OP_IS_CURRENT:
@@ -12872,7 +12924,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
- String packageName) {
+ String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
if (isUnicornFlagEnabled()) {
@@ -12891,6 +12943,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
+ } else if (Flags.dmrhCanSetAppRestriction()) {
+ final boolean isRoleHolder;
+ if (who != null) {
+ // Caller is DO or PO. They cannot call this on parent
+ Preconditions.checkCallAuthorization(!parent
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Caller is delegates or the DMRH. Only DMRH can call this on parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
+ mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ affectedUserId);
+ if (!policies.containsKey(enforcingAdmin)) {
+ return Bundle.EMPTY;
+ }
+ return policies.get(enforcingAdmin).getValue();
+ } else {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
+ caller.getUserHandle());
+ // if no restrictions were saved, mUserManager.getApplicationRestrictions
+ // returns null, but DPM method should return an empty Bundle as per JavaDoc
+ return bundle != null ? bundle : Bundle.EMPTY;
+ });
+ }
+
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
@@ -15811,19 +15907,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
for (EnforcingAdmin admin : policies.keySet()) {
restrictions.add(policies.get(admin).getValue());
}
- if (!restrictions.isEmpty()) {
- return restrictions;
- }
return mInjector.binderWithCleanCallingIdentity(() -> {
- // Could be a device that has a DPC that hasn't migrated yet, so just return any
+ // Could be a device that has a DPC that hasn't migrated yet, so also return any
// restrictions saved in userManager.
Bundle bundle = mUserManager.getApplicationRestrictions(
packageName, UserHandle.of(userId));
- if (bundle == null || bundle.isEmpty()) {
- return new ArrayList<>();
+ if (bundle != null && !bundle.isEmpty()) {
+ restrictions.add(bundle);
}
- return List.of(bundle);
+ return restrictions;
});
}
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
index 6ad8d790485c..6393e11b7432 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "device_state_flags",
package: "com.android.server.policy.feature.flags",
+ container: "system",
srcs: [
"device_state_flags.aconfig",
],
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 29e258cc90ff..21e33dd1b99a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.policy.feature.flags"
+container: "system"
flag {
name: "enable_dual_display_blocking"
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0a7f49da0c31..3c6b500d9ced 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,29 +108,50 @@ import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
import com.android.server.adaptiveauth.AdaptiveAuthService;
+import com.android.server.adb.AdbService;
+import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
+import com.android.server.ambientcontext.AmbientContextManagerService;
+import com.android.server.app.GameManagerService;
import com.android.server.appbinding.AppBindingService;
+import com.android.server.apphibernation.AppHibernationService;
import com.android.server.appop.AppOpMigrationHelper;
import com.android.server.appop.AppOpMigrationHelperImpl;
+import com.android.server.appprediction.AppPredictionManagerService;
+import com.android.server.appwidget.AppWidgetService;
import com.android.server.art.ArtModuleServiceInitializer;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.attention.AttentionManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.autofill.AutofillManagerService;
+import com.android.server.backup.BackupManagerService;
import com.android.server.biometrics.AuthService;
import com.android.server.biometrics.BiometricService;
import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.biometrics.sensors.iris.IrisService;
+import com.android.server.blob.BlobStoreManagerService;
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.clipboard.ClipboardService;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.virtual.VirtualDeviceManagerService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
+import com.android.server.compat.overrides.AppCompatOverridesService;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.PacProxyService;
+import com.android.server.content.ContentService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
+import com.android.server.contextualsearch.ContextualSearchManagerService;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
+import com.android.server.credentials.CredentialManagerService;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
@@ -147,14 +168,20 @@ import com.android.server.incident.IncidentCompanionService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.integrity.AppIntegrityManagerService;
+import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsService;
import com.android.server.locales.LocaleManagerService;
import com.android.server.location.LocationManagerService;
import com.android.server.location.altitude.AltitudeService;
+import com.android.server.locksettings.LockSettingsService;
import com.android.server.logcat.LogcatManagerService;
+import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaSessionService;
import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.midi.MidiService;
+import com.android.server.musicrecognition.MusicRecognitionManagerService;
import com.android.server.net.NetworkManagementService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.watchlist.NetworkWatchlistService;
@@ -195,12 +222,16 @@ import com.android.server.power.ShutdownThread;
import com.android.server.power.ThermalManagerService;
import com.android.server.power.hint.HintManagerService;
import com.android.server.powerstats.PowerStatsService;
+import com.android.server.print.PrintManagerService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
import com.android.server.resources.ResourcesManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleServicePlatformHelper;
+import com.android.server.rollback.RollbackManagerService;
import com.android.server.rotationresolver.RotationResolverManagerService;
+import com.android.server.search.SearchManagerService;
+import com.android.server.searchui.SearchUiManagerService;
import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -210,16 +241,28 @@ import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
+import com.android.server.slice.SliceManagerService;
+import com.android.server.smartspace.SmartspaceManagerService;
import com.android.server.soundtrigger.SoundTriggerService;
import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
+import com.android.server.speech.SpeechRecognitionManagerService;
+import com.android.server.stats.bootstrap.StatsBootstrapAtomService;
+import com.android.server.stats.pull.StatsPullAtomService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.systemcaptions.SystemCaptionsManagerService;
import com.android.server.telecom.TelecomLoaderService;
import com.android.server.testharness.TestHarnessModeService;
import com.android.server.textclassifier.TextClassificationManagerService;
import com.android.server.textservices.TextServicesManagerService;
+import com.android.server.texttospeech.TextToSpeechManagerService;
+import com.android.server.timedetector.GnssTimeUpdateService;
import com.android.server.timedetector.NetworkTimeUpdateService;
+import com.android.server.timedetector.TimeDetectorService;
+import com.android.server.timezonedetector.TimeZoneDetectorService;
+import com.android.server.timezonedetector.location.LocationTimeZoneManagerService;
import com.android.server.tracing.TracingServiceProxy;
+import com.android.server.translation.TranslationManagerService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -227,10 +270,15 @@ import com.android.server.tv.interactive.TvInteractiveAppManagerService;
import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.uri.UriGrantsManagerService;
+import com.android.server.usage.StorageStatsService;
import com.android.server.usage.UsageStatsService;
+import com.android.server.usb.UsbService;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.vibrator.VibratorManagerService;
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import com.android.server.vr.VrManagerService;
+import com.android.server.wallpaper.WallpaperManagerService;
+import com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService;
import com.android.server.wearable.WearableSensingManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -268,44 +316,20 @@ public final class SystemServer implements Dumpable {
* Implementation class names. TODO: Move them to a codegen class or load
* them from the build system somehow.
*/
- private static final String BACKUP_MANAGER_SERVICE_CLASS =
- "com.android.server.backup.BackupManagerService$Lifecycle";
- private static final String APPWIDGET_SERVICE_CLASS =
- "com.android.server.appwidget.AppWidgetService";
private static final String ARC_NETWORK_SERVICE_CLASS =
"com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
"com.android.server.arc.health.ArcSystemHealthService";
- private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.voiceinteraction.VoiceInteractionManagerService";
- private static final String APP_HIBERNATION_SERVICE_CLASS =
- "com.android.server.apphibernation.AppHibernationService";
- private static final String PRINT_MANAGER_SERVICE_CLASS =
- "com.android.server.print.PrintManagerService";
- private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.CompanionDeviceManagerService";
- private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.virtual.VirtualDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanion$Lifecycle";
private static final String SCHEDULING_APEX_PATH =
"/apex/com.android.scheduling/javalib/service-scheduling.jar";
private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
"com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
- private static final String CONNECTIVITY_SERVICE_APEX_PATH =
- "/apex/com.android.tethering/javalib/service-connectivity.jar";
- private static final String STATS_COMPANION_LIFECYCLE_CLASS =
- "com.android.server.stats.StatsCompanion$Lifecycle";
- private static final String STATS_PULL_ATOM_SERVICE_CLASS =
- "com.android.server.stats.pull.StatsPullAtomService";
- private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS =
- "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle";
- private static final String USB_SERVICE_CLASS =
- "com.android.server.usb.UsbService$Lifecycle";
- private static final String MIDI_SERVICE_CLASS =
- "com.android.server.midi.MidiService$Lifecycle";
private static final String WIFI_APEX_SERVICE_JAR_PATH =
"/apex/com.android.wifi/javalib/service-wifi.jar";
private static final String WIFI_SERVICE_CLASS =
@@ -320,16 +344,6 @@ public final class SystemServer implements Dumpable {
"com.android.server.wifi.p2p.WifiP2pService";
private static final String LOWPAN_SERVICE_CLASS =
"com.android.server.lowpan.LowpanService";
- private static final String JOB_SCHEDULER_SERVICE_CLASS =
- "com.android.server.job.JobSchedulerService";
- private static final String LOCK_SETTINGS_SERVICE_CLASS =
- "com.android.server.locksettings.LockSettingsService$Lifecycle";
- private static final String STORAGE_MANAGER_SERVICE_CLASS =
- "com.android.server.StorageManagerService$Lifecycle";
- private static final String STORAGE_STATS_SERVICE_CLASS =
- "com.android.server.usage.StorageStatsService$Lifecycle";
- private static final String SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.search.SearchManagerService$Lifecycle";
private static final String THERMAL_OBSERVER_CLASS =
"com.android.clockwork.ThermalObserver";
private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
@@ -354,91 +368,26 @@ public final class SystemServer implements Dumpable {
"com.android.clockwork.settings.WearSettingsService";
private static final String WRIST_ORIENTATION_SERVICE_CLASS =
"com.android.clockwork.wristorientation.WristOrientationService";
- private static final String ACCOUNT_SERVICE_CLASS =
- "com.android.server.accounts.AccountManagerService$Lifecycle";
- private static final String CONTENT_SERVICE_CLASS =
- "com.android.server.content.ContentService$Lifecycle";
- private static final String WALLPAPER_SERVICE_CLASS =
- "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
- private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
- "com.android.server.autofill.AutofillManagerService";
- private static final String CREDENTIAL_MANAGER_SERVICE_CLASS =
- "com.android.server.credentials.CredentialManagerService";
- private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
- "com.android.server.contentcapture.ContentCaptureManagerService";
- private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
- "com.android.server.translation.TranslationManagerService";
- private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.musicrecognition.MusicRecognitionManagerService";
- private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
- "com.android.server.ambientcontext.AmbientContextManagerService";
- private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
- "com.android.server.systemcaptions.SystemCaptionsManagerService";
- private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
- "com.android.server.texttospeech.TextToSpeechManagerService";
+
private static final String IOT_SERVICE_CLASS =
"com.android.things.server.IoTSystemService";
- private static final String SLICE_MANAGER_SERVICE_CLASS =
- "com.android.server.slice.SliceManagerService$Lifecycle";
private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
"com.android.internal.car.CarServiceHelperService";
- private static final String TIME_DETECTOR_SERVICE_CLASS =
- "com.android.server.timedetector.TimeDetectorService$Lifecycle";
- private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
- "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
- private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
- "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle";
- private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
- "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
- private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
- "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
- private static final String ADB_SERVICE_CLASS =
- "com.android.server.adb.AdbService$Lifecycle";
- private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.speech.SpeechRecognitionManagerService";
- private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
- "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
- private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
- "com.android.server.appprediction.AppPredictionManagerService";
- private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
- "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
- private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
- "com.android.server.searchui.SearchUiManagerService";
- private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
- "com.android.server.smartspace.SmartspaceManagerService";
- private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.contextualsearch.ContextualSearchManagerService";
- private static final String DEVICE_IDLE_CONTROLLER_CLASS =
- "com.android.server.DeviceIdleController";
- private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
- "com.android.server.blob.BlobStoreManagerService";
private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS =
"com.android.server.appsearch.AppSearchModule$Lifecycle";
private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
"com.android.server.compos.IsolatedCompilationService";
- private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
- "com.android.server.rollback.RollbackManagerService";
- private static final String ALARM_MANAGER_SERVICE_CLASS =
- "com.android.server.alarm.AlarmManagerService";
- private static final String MEDIA_SESSION_SERVICE_CLASS =
- "com.android.server.media.MediaSessionService";
- private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS =
- "com.android.server.media.MediaResourceMonitorService";
+ private static final String CONNECTIVITY_SERVICE_APEX_PATH =
+ "/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
"com.android.server.ConnectivityServiceInitializer";
private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
"com.android.server.NetworkStatsServiceInitializer";
- private static final String IP_CONNECTIVITY_METRICS_CLASS =
- "com.android.server.connectivity.IpConnectivityMetrics";
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
"com.android.server.media.MediaCommunicationService";
- private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
- "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS =
"com.android.server.healthconnect.HealthConnectManagerService";
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
- private static final String GAME_MANAGER_SERVICE_CLASS =
- "com.android.server.app.GameManagerService$Lifecycle";
private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
"com.android.ecm.EnhancedConfirmationService";
@@ -461,6 +410,7 @@ public final class SystemServer implements Dumpable {
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+
private static final String DEVICE_LOCK_SERVICE_CLASS =
"com.android.server.devicelock.DeviceLockService";
private static final String DEVICE_LOCK_APEX_PATH =
@@ -1435,7 +1385,7 @@ public final class SystemServer implements Dumpable {
// Manages apk rollbacks.
t.traceBegin("StartRollbackManagerService");
- mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(RollbackManagerService.class);
t.traceEnd();
// Tracks native tombstones.
@@ -1580,11 +1530,11 @@ public final class SystemServer implements Dumpable {
// The AccountManager must come before the ContentService
t.traceBegin("StartAccountManagerService");
- mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccountManagerService.Lifecycle.class);
t.traceEnd();
t.traceBegin("StartContentService");
- mSystemServiceManager.startService(CONTENT_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentService.Lifecycle.class);
t.traceEnd();
t.traceBegin("InstallSystemProviders");
@@ -1639,7 +1589,7 @@ public final class SystemServer implements Dumpable {
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartAlarmManagerService");
- mSystemServiceManager.startService(ALARM_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AlarmManagerService.class);
t.traceEnd();
t.traceBegin("StartInputManagerService");
@@ -1721,7 +1671,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("IpConnectivityMetrics");
- mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS);
+ mSystemServiceManager.startService(IpConnectivityMetrics.class);
t.traceEnd();
t.traceBegin("NetworkWatchlistService");
@@ -1796,7 +1746,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartAccessibilityManagerService");
try {
- mSystemServiceManager.startService(ACCESSIBILITY_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccessibilityManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Accessibility Manager", e);
}
@@ -1819,7 +1769,7 @@ public final class SystemServer implements Dumpable {
* NotificationManagerService is dependant on StorageManagerService,
* (for media / usb notifications) so we must start StorageManagerService first.
*/
- mSystemServiceManager.startService(STORAGE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageManagerService.Lifecycle.class);
storageManager = IStorageManager.Stub.asInterface(
ServiceManager.getService("mount"));
} catch (Throwable e) {
@@ -1829,7 +1779,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartStorageStatsService");
try {
- mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageStatsService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting StorageStatsService", e);
}
@@ -1860,7 +1810,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("StartAppHibernationService");
- mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppHibernationService.class);
t.traceEnd();
t.traceBegin("ArtManagerLocal");
@@ -1892,7 +1842,7 @@ public final class SystemServer implements Dumpable {
} else {
t.traceBegin("StartLockSettingsService");
try {
- mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+ mSystemServiceManager.startService(LockSettingsService.Lifecycle.class);
lockSettings = ILockSettings.Stub.asInterface(
ServiceManager.getService("lock_settings"));
} catch (Throwable e) {
@@ -1925,7 +1875,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartDeviceIdleController");
- mSystemServiceManager.startService(DEVICE_IDLE_CONTROLLER_CLASS);
+ mSystemServiceManager.startService(DeviceIdleController.class);
t.traceEnd();
// Always start the Device Policy Manager, so that the API is compatible with
@@ -1948,7 +1898,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultMusicRecognitionService)) {
t.traceBegin("StartMusicRecognitionManagerService");
- mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(MusicRecognitionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG,
@@ -1966,7 +1916,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
t.traceBegin("StartAmbientContextService");
- mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AmbientContextManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag");
@@ -1974,13 +1924,13 @@ public final class SystemServer implements Dumpable {
// System Speech Recognition Service
t.traceBegin("StartSpeechRecognitionManagerService");
- mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SpeechRecognitionManagerService.class);
t.traceEnd();
// App prediction manager service
if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
t.traceBegin("StartAppPredictionService");
- mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppPredictionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AppPredictionService not defined by OEM");
@@ -1989,7 +1939,7 @@ public final class SystemServer implements Dumpable {
// Content suggestions manager service
if (deviceHasConfigString(context, R.string.config_defaultContentSuggestionsService)) {
t.traceBegin("StartContentSuggestionsService");
- mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentSuggestionsManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContentSuggestionsService not defined by OEM");
@@ -1998,14 +1948,14 @@ public final class SystemServer implements Dumpable {
// Search UI manager service
if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) {
t.traceBegin("StartSearchUiService");
- mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchUiManagerService.class);
t.traceEnd();
}
// Smartspace manager service
if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) {
t.traceBegin("StartSmartspaceService");
- mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SmartspaceManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
@@ -2015,7 +1965,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultContextualSearchPackageName)) {
t.traceBegin("StartContextualSearchService");
- mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContextualSearchManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
@@ -2214,7 +2164,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartTimeDetectorService");
try {
- mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeDetectorService service", e);
}
@@ -2235,7 +2185,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartTimeZoneDetectorService");
try {
- mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeZoneDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeZoneDetectorService service", e);
}
@@ -2251,7 +2201,7 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartLocationTimeZoneManagerService");
try {
- mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(LocationTimeZoneManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting LocationTimeZoneManagerService service", e);
}
@@ -2260,7 +2210,7 @@ public final class SystemServer implements Dumpable {
if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) {
t.traceBegin("StartGnssTimeUpdateService");
try {
- mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS);
+ mSystemServiceManager.startService(GnssTimeUpdateService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting GnssTimeUpdateService service", e);
}
@@ -2270,7 +2220,7 @@ public final class SystemServer implements Dumpable {
if (!isWatch) {
t.traceBegin("StartSearchManagerService");
try {
- mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Search Service", e);
}
@@ -2279,7 +2229,7 @@ public final class SystemServer implements Dumpable {
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
t.traceBegin("StartWallpaperManagerService");
- mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperManagerService.Lifecycle.class);
t.traceEnd();
} else {
Slog.i(TAG, "Wallpaper service disabled by config");
@@ -2289,8 +2239,7 @@ public final class SystemServer implements Dumpable {
if (deviceHasConfigString(context,
R.string.config_defaultWallpaperEffectsGenerationService)) {
t.traceBegin("StartWallpaperEffectsGenerationService");
- mSystemServiceManager.startService(
- WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperEffectsGenerationManagerService.class);
t.traceEnd();
}
@@ -2345,14 +2294,14 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
// Start MIDI Manager service
t.traceBegin("StartMidiManager");
- mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ mSystemServiceManager.startService(MidiService.Lifecycle.class);
t.traceEnd();
}
// Start ADB Debugging Service
t.traceBegin("StartAdbService");
try {
- mSystemServiceManager.startService(ADB_SERVICE_CLASS);
+ mSystemServiceManager.startService(AdbService.Lifecycle.class);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting AdbService");
}
@@ -2364,7 +2313,7 @@ public final class SystemServer implements Dumpable {
|| Build.IS_EMULATOR) {
// Manage USB host and device support
t.traceBegin("StartUsbService");
- mSystemServiceManager.startService(USB_SERVICE_CLASS);
+ mSystemServiceManager.startService(UsbService.Lifecycle.class);
t.traceEnd();
}
@@ -2396,7 +2345,7 @@ public final class SystemServer implements Dumpable {
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartJobScheduler");
- mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
+ mSystemServiceManager.startService(JobSchedulerService.class);
t.traceEnd();
t.traceBegin("StartSoundTrigger");
@@ -2409,14 +2358,14 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
t.traceBegin("StartBackupManager");
- mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BackupManagerService.Lifecycle.class);
t.traceEnd();
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
|| context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
t.traceBegin("StartAppWidgetService");
- mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppWidgetService.class);
t.traceEnd();
}
@@ -2425,7 +2374,7 @@ public final class SystemServer implements Dumpable {
// of initializing various settings. It will internally modify its behavior
// based on that feature.
t.traceBegin("StartVoiceRecognitionManager");
- mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VoiceInteractionManagerService.class);
t.traceEnd();
if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
@@ -2486,7 +2435,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BlobStoreManagerService.class);
t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
@@ -2507,7 +2456,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
t.traceBegin("StartPrintManager");
- mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(PrintManagerService.class);
t.traceEnd();
}
@@ -2517,13 +2466,13 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
t.traceBegin("StartCompanionDeviceManager");
- mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(CompanionDeviceManagerService.class);
t.traceEnd();
}
if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) {
t.traceBegin("StartVirtualDeviceManager");
- mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VirtualDeviceManagerService.class);
t.traceEnd();
}
@@ -2532,7 +2481,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("StartMediaSessionService");
- mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaSessionService.class);
t.traceEnd();
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -2563,7 +2512,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
t.traceBegin("StartMediaResourceMonitor");
- mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaResourceMonitorService.class);
t.traceEnd();
}
@@ -2735,7 +2684,7 @@ public final class SystemServer implements Dumpable {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
t.traceBegin("StartSliceManagerService");
- mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SliceManagerService.Lifecycle.class);
t.traceEnd();
}
@@ -2759,12 +2708,12 @@ public final class SystemServer implements Dumpable {
// Statsd pulled atoms
t.traceBegin("StartStatsPullAtomService");
- mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
+ mSystemServiceManager.startService(StatsPullAtomService.class);
t.traceEnd();
// Log atoms to statsd from bootstrap processes.
t.traceBegin("StatsBootstrapAtomService");
- mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS);
+ mSystemServiceManager.startService(StatsBootstrapAtomService.Lifecycle.class);
t.traceEnd();
// Incidentd and dumpstated helper
@@ -2811,7 +2760,7 @@ public final class SystemServer implements Dumpable {
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
t.traceBegin("StartAutoFillService");
- mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AutofillManagerService.class);
t.traceEnd();
}
@@ -2820,13 +2769,12 @@ public final class SystemServer implements Dumpable {
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
if (credentialManagerEnabled) {
- if(isWatch &&
- !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
- Slog.d(TAG, "CredentialManager disabled on wear.");
+ if (isWatch && !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+ Slog.d(TAG, "CredentialManager disabled on wear.");
} else {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CredentialManagerService.class);
+ t.traceEnd();
}
} else {
Slog.d(TAG, "CredentialManager disabled.");
@@ -2836,7 +2784,7 @@ public final class SystemServer implements Dumpable {
// Translation manager service
if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) {
t.traceBegin("StartTranslationManagerService");
- mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TranslationManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "TranslationService not defined by OEM");
@@ -2980,7 +2928,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("GameManagerService");
- mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
t.traceEnd();
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
@@ -3012,7 +2960,7 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("AppCompatOverridesService");
- mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppCompatOverridesService.Lifecycle.class);
t.traceEnd();
t.traceBegin("HealthConnectManagerService");
@@ -3397,14 +3345,14 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartSystemCaptionsManagerService");
- mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SystemCaptionsManagerService.class);
t.traceEnd();
}
private void startTextToSpeechManagerService(@NonNull Context context,
@NonNull TimingsTraceAndSlog t) {
t.traceBegin("StartTextToSpeechManagerService");
- mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TextToSpeechManagerService.class);
t.traceEnd();
}
@@ -3439,7 +3387,7 @@ public final class SystemServer implements Dumpable {
}
t.traceBegin("StartContentCaptureService");
- mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentCaptureManagerService.class);
ContentCaptureManagerInternal ccmi =
LocalServices.getService(ContentCaptureManagerInternal.class);
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index 00bfcd3007a4..4de4a56aa806 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -103,6 +103,28 @@
"include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
}
]
+ },
+ {
+ "name": "CtsVirtualDevicesAudioTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsVirtualDevicesAppLaunchTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
+ }
+ ]
}
],
"imports": [
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 36bea7d2eea2..36758341d4d7 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,6 +16,7 @@
package com.android.server.permission.access
+import android.permission.flags.Flags
import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -78,6 +79,9 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
mutateAppIdPackageNames()
.mutateOrPut(packageState.appId) { MutableIndexedListSet() }
.add(packageState.packageName)
@@ -103,6 +107,9 @@ private constructor(
newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
forEachSchemePolicy { with(it) { onUserAdded(userId) } }
newState.externalState.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
upgradePackageVersion(packageState, userId)
}
}
@@ -126,6 +133,9 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (packageName, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
if (packageState.volumeUuid == volumeUuid) {
// The APK for a package on a mounted storage volume may still be unavailable
// due to APK being deleted, e.g. after an OTA.
@@ -151,6 +161,9 @@ private constructor(
with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
}
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
if (packageState.volumeUuid == volumeUuid) {
newState.userStates.forEachIndexed { _, userId, _ ->
upgradePackageVersion(packageState, userId)
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 47fd970c760e..63fb468c8f54 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -81,6 +81,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.externalState.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
}
newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b32c54496002..44ed3df34f24 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1445,6 +1445,9 @@ class PermissionService(private val service: AccessCheckingService) :
val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
service.mutateState {
packageStates.forEach { (packageName, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
val androidPackage = packageState.androidPackage ?: return@forEach
androidPackage.requestedPermissions.forEach { permissionName ->
updatePermissionFlags(
@@ -1598,7 +1601,7 @@ class PermissionService(private val service: AccessCheckingService) :
) {
with(policy) { getPermissionFlags(appId, userId, permissionName) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1623,7 +1626,7 @@ class PermissionService(private val service: AccessCheckingService) :
) {
with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1877,6 +1880,9 @@ class PermissionService(private val service: AccessCheckingService) :
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
service.mutateState {
snapshot.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) }
}
@@ -1918,8 +1924,11 @@ class PermissionService(private val service: AccessCheckingService) :
}
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
- snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
- val androidPackage = packageState.androidPackage ?: return@packageStates
+ snapshot.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
+ val androidPackage = packageState.androidPackage ?: return@forEach
if (permissionName in androidPackage.requestedPermissions) {
packageNames += androidPackage.packageName
}
@@ -1934,6 +1943,9 @@ class PermissionService(private val service: AccessCheckingService) :
val permissions = service.getState { with(policy) { getPermissions() } }
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@packageStates
+ }
val androidPackage = packageState.androidPackage ?: return@packageStates
androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName ->
val permission = permissions[permissionName] ?: return@requestedPermissions
@@ -2060,6 +2072,9 @@ class PermissionService(private val service: AccessCheckingService) :
val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
appIdPackageNames
.getOrPut(packageState.appId) { MutableIndexedSet() }
.add(packageState.packageName)
@@ -2313,6 +2328,10 @@ class PermissionService(private val service: AccessCheckingService) :
isInstantApp: Boolean,
oldPackage: AndroidPackage?
) {
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return
+ }
+
synchronized(storageVolumeLock) {
// Accumulating the package names here because we want to maintain the same call order
// of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the
@@ -2339,6 +2358,10 @@ class PermissionService(private val service: AccessCheckingService) :
params: PermissionManagerServiceInternal.PackageInstalledParams,
userId: Int
) {
+ if (Flags.ignoreApexPermissions() && androidPackage.isApex) {
+ return
+ }
+
if (params === PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT) {
// TODO: We should actually stop calling onPackageInstalled() when we are passing
// PackageInstalledParams.DEFAULT in InstallPackageHelper, because there's actually no
@@ -2391,6 +2414,10 @@ class PermissionService(private val service: AccessCheckingService) :
sharedUserPkgs: List<AndroidPackage>,
userId: Int
) {
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return
+ }
+
val userIds =
if (userId == UserHandle.USER_ALL) {
userManagerService.userIdsIncludingPreCreated
@@ -2820,15 +2847,6 @@ class PermissionService(private val service: AccessCheckingService) :
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
- /** These permissions are supported for virtual devices. */
- // TODO: b/298661870 - Use new API to get the list of device aware permissions.
- val DEVICE_AWARE_PERMISSIONS =
- if (Flags.deviceAwarePermissionsEnabled()) {
- setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
- } else {
- emptySet<String>()
- }
-
fun getFullerPermission(permissionName: String): String? =
FULLER_PERMISSIONS[permissionName]
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54de64e2f3a8..64253e10bd21 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 494a6677e633..b80d44fb8fd8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -169,53 +170,57 @@ public final class DisplayDeviceConfigTest {
assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
// Test thresholds
- assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
- ZERO_DELTA);
- assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
- ZERO_DELTA);
- assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
- assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
- assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+ HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+ HysteresisLevels ambientIdleHysteresis =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+ HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+ HysteresisLevels screenIdleHysteresis =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
+ assertEquals(10, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(20, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(30, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(40, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
+
+ assertEquals(0.1f, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0.2f, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0.3f, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0.4f, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.10f, 0.20f},
- mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{9, 10, 11},
- mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ screenHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.09f, 0.10f, 0.11f},
+ screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.11f, 0.21f},
- mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{11, 12, 13},
- mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+ screenHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.11f, 0.12f, 0.13f},
+ screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 100, 200},
- mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{13, 14, 15},
- mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.13f, 0.14f, 0.15f},
+ ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 300, 400},
- mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{15, 16, 17},
- mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.15f, 0.16f, 0.17f},
+ ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.12f, 0.22f},
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{17, 18, 19},
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.17f, 0.18f, 0.19f},
+ screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.13f, 0.23f},
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{19, 20, 21},
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.19f, 0.20f, 0.21f},
+ screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 500, 600},
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{21, 22, 23},
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.21f, 0.22f, 0.23f},
+ ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 700, 800},
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{23, 24, 25},
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.23f, 0.24f, 0.25f},
+ ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
@@ -686,55 +691,60 @@ public final class DisplayDeviceConfigTest {
new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)}, SMALL_DELTA);
+ HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+ HysteresisLevels ambientIdleHysteresis =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+ HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+ HysteresisLevels screenIdleHysteresis =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
// Test thresholds
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
- ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
// screen levels will be considered "old screen brightness scale"
// and therefore will divide by 255
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
- assertArrayEquals(new float[]{35, 36, 37},
- mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ screenHysteresis.getBrighteningThresholdLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+ screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
- assertArrayEquals(new float[]{37, 38, 39},
- mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+ screenHysteresis.getDarkeningThresholdLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+ screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{27, 28, 29},
- mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+ ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{29, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+ ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
- assertArrayEquals(new float[]{35, 36, 37},
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+ screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
- assertArrayEquals(new float[]{37, 38, 39},
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+ screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{27, 28, 29},
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+ ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{29, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+ ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index 1c71abc984df..00296745a81f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -72,6 +73,7 @@ public class DisplayDeviceTest {
@Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mDisplayDeviceInfo.xDpi = 0.5f;
mDisplayDeviceInfo.yDpi = 1.0f;
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
@@ -81,6 +83,16 @@ public class DisplayDeviceTest {
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_noAnisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -97,6 +109,7 @@ public class DisplayDeviceTest {
@Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mDisplayDeviceInfo.xDpi = 0.5f;
mDisplayDeviceInfo.yDpi = 1.0f;
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
@@ -107,6 +120,17 @@ public class DisplayDeviceTest {
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_noAnisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index afb87d1df798..de939146f68e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -82,6 +82,7 @@ import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
@@ -1954,6 +1955,14 @@ public final class DisplayPowerControllerTest {
.thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE);
when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis())
.thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
+
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+ when(displayDeviceConfigMock.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(displayDeviceConfigMock.getAmbientBrightnessIdleHysteresis()).thenReturn(
+ hysteresisLevels);
+ when(displayDeviceConfigMock.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(displayDeviceConfigMock.getScreenBrightnessIdleHysteresis()).thenReturn(
+ hysteresisLevels);
}
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
@@ -1995,7 +2004,7 @@ public final class DisplayPowerControllerTest {
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController,
+ screenOffBrightnessSensorController,
hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
@@ -2005,6 +2014,11 @@ public final class DisplayPowerControllerTest {
final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+ when(config.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getAmbientBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getScreenBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
@@ -2080,7 +2094,6 @@ public final class DisplayPowerControllerTest {
private final AutomaticBrightnessController mAutomaticBrightnessController;
private final WakelockController mWakelockController;
private final BrightnessMappingStrategy mBrightnessMappingStrategy;
- private final HysteresisLevels mHysteresisLevels;
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
@@ -2096,7 +2109,6 @@ public final class DisplayPowerControllerTest {
AutomaticBrightnessController automaticBrightnessController,
WakelockController wakelockController,
BrightnessMappingStrategy brightnessMappingStrategy,
- HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
NormalBrightnessModeController normalBrightnessModeController,
@@ -2108,7 +2120,6 @@ public final class DisplayPowerControllerTest {
mAutomaticBrightnessController = automaticBrightnessController;
mWakelockController = wakelockController;
mBrightnessMappingStrategy = brightnessMappingStrategy;
- mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
mNormalBrightnessModeController = normalBrightnessModeController;
@@ -2186,22 +2197,6 @@ public final class DisplayPowerControllerTest {
}
@Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return mHysteresisLevels;
- }
-
- @Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return mHysteresisLevels;
- }
-
- @Override
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager, Sensor lightSensor, Handler handler,
ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
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 5a50510082d6..1a03e780521a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -79,12 +79,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.foldables.FoldGracePeriodProvider;
+import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.FoldSettingProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -124,6 +128,9 @@ public class LogicalDisplayMapperTest {
private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock FoldSettingProvider mFoldSettingProviderMock;
@@ -133,6 +140,7 @@ public class LogicalDisplayMapperTest {
@Mock IThermalService mIThermalServiceMock;
@Mock DisplayManagerFlags mFlagsMock;
@Mock DisplayAdapter mDisplayAdapterMock;
+ @Mock WindowManagerPolicy mWindowManagerPolicy;
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@Captor ArgumentCaptor<Integer> mDisplayEventCaptor;
@@ -143,6 +151,9 @@ public class LogicalDisplayMapperTest {
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mLocalServiceKeeperRule.overrideLocalService(WindowManagerPolicy.class,
+ mWindowManagerPolicy);
+
mDeviceStateToLayoutMapSpy =
spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE));
mDisplayDeviceRepo = new DisplayDeviceRepository(
@@ -194,6 +205,7 @@ public class LogicalDisplayMapperTest {
mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
mDeviceStateToLayoutMapSpy, mFlagsMock);
+ mLogicalDisplayMapper.onWindowManagerReady();
}
@@ -757,6 +769,44 @@ public class LogicalDisplayMapperTest {
}
@Test
+ public void testDisplaySwappedAfterDeviceStateChange_windowManagerIsNotified() {
+ FoldableDisplayDevices foldableDisplayDevices = createFoldableDeviceStateToLayoutMap();
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN);
+ mLogicalDisplayMapper.onEarlyInteractivityChange(true);
+ mLogicalDisplayMapper.onBootCompleted();
+ advanceTime(1000);
+ clearInvocations(mWindowManagerPolicy);
+
+ // Switch from 'inner' to 'outer' display (fold a foldable device)
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
+ // Continue folding device state transition by turning off the inner display
+ foldableDisplayDevices.mInner.setState(STATE_OFF);
+ notifyDisplayChanges(foldableDisplayDevices.mOuter);
+ advanceTime(TIMEOUT_STATE_TRANSITION_MILLIS);
+
+ verify(mWindowManagerPolicy).onDisplaySwitchStart(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testCreateNewLogicalDisplay_windowManagerIsNotNotifiedAboutSwitch() {
+ DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+ LogicalDisplay display1 = add(device1);
+
+ assertTrue(display1.isEnabledLocked());
+
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
+ add(device2);
+
+ // As it is not a display switch but adding a new display, we should not notify
+ // about display switch start to window manager
+ verify(mWindowManagerPolicy, never()).onDisplaySwitchStart(anyInt());
+ }
+
+ @Test
public void testDoNotWaitForSleepWhenFoldSettingStayAwake() {
// Test device should be marked ready for transition immediately when 'Continue using app
// on fold' set to 'Always'
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index e798aa20f4bf..779445e66780 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -142,8 +142,39 @@ public class LogicalDisplayTest {
assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
}
+
+ @Test
+ public void testNoLetterbox_noAnisotropyCorrectionForInternalDisplay() {
+ mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true,
+ /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width not scaled
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+ // correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
@Test
public void testNoLetterbox_anisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
/*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -172,6 +203,7 @@ public class LogicalDisplayTest {
@Test
public void testLetterbox_anisotropyCorrectionYDpi() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
/*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -229,6 +261,7 @@ public class LogicalDisplayTest {
@Test
public void testPillarbox_anisotropyCorrection() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
/*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -257,6 +290,7 @@ public class LogicalDisplayTest {
@Test
public void testNoPillarbox_anisotropyCorrectionYDpi() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
/*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -318,6 +352,7 @@ public class LogicalDisplayTest {
@Test
public void testGetDisplayPositionAlwaysRotateDisplayEnabled() {
+ mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
/*isAnisotropyCorrectionEnabled=*/ true,
/*isAlwaysRotateDisplayDeviceEnabled=*/ true);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
index c379d6b79ee7..3fd3cef07dd5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -43,6 +43,11 @@ public class NormalBrightnessModeControllerTest {
private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // NormalBrightnessModeController is temporary disabled if auto brightness is off,
+ // to avoid capping brightness based on stale ambient lux. Temporary disabling tests with
+ // auto brightness off and default config pres
+ // The issue is tracked here: b/322445088
@Keep
private static Object[][] brightnessData() {
return new Object[][]{
@@ -59,10 +64,10 @@ public class NormalBrightnessModeControllerTest {
ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
), 0.2f},
// Auto brightness - off, config only for default
- {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
- BrightnessLimitMapType.DEFAULT,
- ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
- ), 0.2f},
+ // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ // BrightnessLimitMapType.DEFAULT,
+ // ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ // ), 0.2f},
// Auto brightness - off, config only for adaptive
{100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
BrightnessLimitMapType.ADAPTIVE,
@@ -81,12 +86,12 @@ public class NormalBrightnessModeControllerTest {
ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
), 0.4f},
// Auto brightness - off, config for both
- {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
- BrightnessLimitMapType.DEFAULT,
- ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
- BrightnessLimitMapType.ADAPTIVE,
- ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
- ), 0.2f},
+ // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ // BrightnessLimitMapType.DEFAULT,
+ // ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ // BrightnessLimitMapType.ADAPTIVE,
+ // ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ // ), 0.2f},
// Auto brightness - on, config for both, ambient high
{1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
BrightnessLimitMapType.DEFAULT,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 87fc7a484c5c..39ffe5be5882 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -33,6 +33,7 @@ import android.os.PowerManager;
import androidx.test.filters.SmallTest;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -230,6 +231,11 @@ public class HdrClamperTest {
}
private void configureClamper() {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // HdrClamper is temporary disabled if auto brightness is off.
+ // Temporary setting AutoBrightnessState to enabled for this test
+ // The issue is tracked here: b/322445088
+ mHdrClamper.setAutoBrightnessState(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
mHdrChangeListener.onHdrVisible(true);
}
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
index a918be2292af..8bdfc500fdc2 100644
--- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java
@@ -69,6 +69,13 @@ import java.util.Set;
public class AudioManagerRouteControllerTest {
private static final String FAKE_ROUTE_NAME = "fake name";
+
+ /**
+ * The number of milliseconds to wait for an asynchronous operation before failing an associated
+ * assertion.
+ */
+ private static final int ASYNC_CALL_TIMEOUTS_MS = 1000;
+
private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER =
createAudioDeviceInfo(
AudioSystem.DEVICE_OUT_SPEAKER, "name_builtin", /* address= */ null);
@@ -231,7 +238,7 @@ public class AudioManagerRouteControllerTest {
MediaRoute2Info builtInSpeakerRoute =
getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
mControllerUnderTest.transferTo(builtInSpeakerRoute.getId());
- verify(mMockAudioManager)
+ verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
.setPreferredDeviceForStrategy(
mMediaAudioProductStrategy,
createAudioDeviceAttribute(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER));
@@ -239,7 +246,7 @@ public class AudioManagerRouteControllerTest {
MediaRoute2Info wiredHeadsetRoute =
getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET);
mControllerUnderTest.transferTo(wiredHeadsetRoute.getId());
- verify(mMockAudioManager)
+ verify(mMockAudioManager, Mockito.timeout(ASYNC_CALL_TIMEOUTS_MS))
.setPreferredDeviceForStrategy(
mMediaAudioProductStrategy,
createAudioDeviceAttribute(AudioDeviceInfo.TYPE_WIRED_HEADSET));
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index f80156021408..0eb8639bd005 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,5 +1,5 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
-per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS
+per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 682569f1d9ab..697548cbe2b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -1111,16 +1111,9 @@ public class RescuePartyTest {
// mock properties in BootThreshold
try {
- if (Flags.recoverabilityDetection()) {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
- PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
- } else {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
- }
mCrashRecoveryPropertiesMap = new HashMap<>();
doAnswer((Answer<Integer>) invocationOnMock -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index 506514469338..edee8cd217d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -34,6 +33,7 @@ import static org.mockito.Mockito.when;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
+import android.os.Process;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -79,7 +79,8 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one";
private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two";
- private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package";
+ private static final String SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package";
+ private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "exempt.screen.recorder.package";
private static final int NOTIFICATION_UID_1 = 5;
private static final int NOTIFICATION_UID_2 = 6;
@@ -281,10 +282,18 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
.getActiveNotifications();
}
+ private MediaProjectionInfo createMediaProjectionInfo() {
+ return new MediaProjectionInfo(SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null);
+ }
+
+ private MediaProjectionInfo createExemptMediaProjectionInfo() {
+ return new MediaProjectionInfo(
+ EXEMPTED_SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null);
+ }
+
@Test
public void mediaProjectionOnStart_verifyExemptedRecorderPackage() {
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
- when(mediaProjectionInfo.getPackageName()).thenReturn(EXEMPTED_SCREEN_RECORDER_PACKAGE);
+ MediaProjectionInfo mediaProjectionInfo = createExemptMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -295,7 +304,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -304,18 +313,18 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void mediaProjectionOnStart_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -323,7 +332,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromSamePackageAndUid();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -333,7 +342,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentPackage();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -343,7 +352,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentUid();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -352,7 +361,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() {
setupSensitiveNotification();
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
Mockito.reset(mWindowManager);
@@ -365,7 +374,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
@@ -381,9 +390,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getActiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -392,9 +401,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -403,9 +412,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -416,9 +425,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -426,7 +435,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
mockDisabledViaDevelopOption();
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verifyZeroInteractions(mWindowManager);
}
@@ -447,8 +456,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
@@ -461,7 +471,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
@@ -472,23 +482,23 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
@Test
public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -496,7 +506,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
doReturn(null)
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
@@ -504,7 +514,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -512,7 +522,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
doReturn(mRankingMap)
@@ -521,7 +531,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -530,7 +540,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
verifyZeroInteractions(mWindowManager);
@@ -553,8 +563,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -568,7 +579,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -580,25 +591,25 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
@Test
public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -606,13 +617,13 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(null);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -620,7 +631,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
doReturn(mRankingMap)
@@ -630,7 +641,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -638,7 +649,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
doThrow(SecurityException.class)
@@ -648,7 +659,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -657,7 +668,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
@@ -681,8 +692,9 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -696,7 +708,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -712,7 +724,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -726,7 +738,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -740,7 +752,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -754,7 +766,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
@@ -770,7 +782,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest {
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index a7430e563904..419bcb8650a7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -38,6 +38,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
import static com.android.server.am.ActivityManagerService.Injector;
@@ -692,6 +693,31 @@ public class ActivityManagerServiceTest {
assertEquals(uid, -1);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testFifoSwitch() {
+ addUidRecord(TEST_UID, TEST_PACKAGE);
+ final ProcessRecord fifoProc = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+ final var wpc = fifoProc.getWindowProcessController();
+ spyOn(wpc);
+ doReturn(true).when(wpc).useFifoUiScheduling();
+ fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+ assertTrue(fifoProc.useFifoUiScheduling());
+ assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+
+ // If there is a request to use more CPU resource (e.g. camera), the current fifo process
+ // should switch the capability of using fifo.
+ final UidRecord uidRecord = addUidRecord(TEST_UID + 1, TEST_PACKAGE + 1);
+ uidRecord.setCurProcState(PROCESS_STATE_TOP);
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), false /* allowSpecifiedFifo */);
+ assertFalse(fifoProc.useFifoUiScheduling());
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), true /* allowSpecifiedFifo */);
+ assertTrue(fifoProc.useFifoUiScheduling());
+
+ fifoProc.makeInactive(mAms.mProcessStats);
+ assertFalse(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+ }
+
@Test
public void testGlobalIsolatedUidAllocator() {
final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 067dd3bf1f7d..637c73f31ce0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -126,6 +126,7 @@ import android.os.BatteryManagerInternal;
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.Handler;
+import android.os.Looper;
import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
@@ -321,6 +322,7 @@ public final class BackgroundRestrictionTest {
private BindServiceEventListener mBindServiceEventListener;
private Context mContext = getInstrumentation().getTargetContext();
+ private Handler mDefaultHandler = new Handler(Looper.getMainLooper());
private TestBgRestrictionInjector mInjector;
private AppRestrictionController mBgRestrictionController;
private AppBatteryTracker mAppBatteryTracker;
@@ -346,10 +348,6 @@ public final class BackgroundRestrictionTest {
mActivityManagerService.mConstants = mActivityManagerConstants;
mPhoneCarrierPrivileges = new PhoneCarrierPrivileges(
mInjector.getTelephonyManager(), MOCK_PRIVILEGED_PACKAGES.length);
- for (int i = 0; i < MOCK_PRIVILEGED_PACKAGES.length; i++) {
- mPhoneCarrierPrivileges.addNewPrivilegePackages(i,
- MOCK_PRIVILEGED_PACKAGES[i], MOCK_PRIVILEGED_UIDS[i]);
- }
doReturn(PROCESS_STATE_FOREGROUND_SERVICE).when(mActivityManagerInternal)
.getUidProcessState(anyInt());
@@ -3048,6 +3046,11 @@ public final class BackgroundRestrictionTest {
@Test
public void testCarrierPrivilegedAppListener() throws Exception {
+ for (int i = 0; i < MOCK_PRIVILEGED_PACKAGES.length; i++) {
+ mPhoneCarrierPrivileges.addNewPrivilegePackages(i,
+ MOCK_PRIVILEGED_PACKAGES[i], MOCK_PRIVILEGED_UIDS[i]);
+ }
+
final long shortMs = 1_000L;
for (int i = 0; i < MOCK_PRIVILEGED_PACKAGES.length; i++) {
verifyPotentialSystemExemptionReason(REASON_CARRIER_PRIVILEGED_APP,
@@ -3356,6 +3359,11 @@ public final class BackgroundRestrictionTest {
}
@Override
+ Handler getDefaultHandler() {
+ return mDefaultHandler;
+ }
+
+ @Override
boolean isTest() {
return true;
}
@@ -3445,6 +3453,16 @@ public final class BackgroundRestrictionTest {
IAppOpsService getIAppOpsService() {
return BackgroundRestrictionTest.this.mIAppOpsService;
}
+
+ @Override
+ int checkPermission(String perm, int pid, int uid) {
+ try {
+ return BackgroundRestrictionTest.this.mIActivityManager.checkPermission(
+ perm, pid, uid);
+ } catch (RemoteException e) {
+ return PERMISSION_DENIED;
+ }
+ }
}
private class TestAppBatteryTrackerInjector extends TestBaseTrackerInjector<AppBatteryPolicy> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907af93c..671472d619d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1978,7 +1978,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2021,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2167,73 @@ public class QuotaControllerTest {
}
@Test
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+ setDischarging();
+
+ JobStatus jobRunning = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+ JobStatus jobPending = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+ setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+ long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+ final ExecutionStats stats;
+ synchronized (mQuotaController.mLock) {
+ stats = mQuotaController.getExecutionStatsLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ assertTrue(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.jobCountLimit);
+ assertEquals(9, stats.bgJobCountInWindow);
+ }
+
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+ trackJobs(jobRunning, jobPending);
+ // UID in the background.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobRunning);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Wait for some extra time to allow for job processing.
+ ArraySet<JobStatus> expected = new ArraySet<>();
+ expected.add(jobPending);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(eq(expected));
+
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+ assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobRunning.isReady());
+ assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+ assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobPending.isReady());
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+ }
+
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+ }
+
+ @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4718,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6618,7 +6685,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index b41568298dbc..0532e04257d4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -55,6 +55,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -63,8 +64,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.security.Authorization;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
import android.service.trust.TrustAgentService;
import android.testing.TestableContext;
import android.view.IWindowManager;
@@ -96,7 +96,6 @@ public class TrustManagerServiceTest {
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(ActivityManager.class)
- .spyStatic(Authorization.class)
.mockStatic(ServiceManager.class)
.mockStatic(WindowManagerGlobal.class)
.build();
@@ -126,14 +125,13 @@ public class TrustManagerServiceTest {
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock FaceManager mFaceManager;
private @Mock FingerprintManager mFingerprintManager;
- private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+ private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock PackageManager mPackageManager;
private @Mock UserManager mUserManager;
private @Mock IWindowManager mWindowManager;
private HandlerThread mHandlerThread;
- private TrustManagerService.Injector mInjector;
private TrustManagerService mService;
private ITrustManager mTrustManager;
@@ -145,8 +143,6 @@ public class TrustManagerServiceTest {
when(mFaceManager.getSensorProperties()).thenReturn(List.of());
when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
- doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
-
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
@@ -193,8 +189,7 @@ public class TrustManagerServiceTest {
mHandlerThread = new HandlerThread("handler");
mHandlerThread.start();
- mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
- mService = new TrustManagerService(mMockContext, mInjector);
+ mService = new TrustManagerService(mMockContext, new MockInjector(mMockContext));
// Get the ITrustManager from the new TrustManagerService.
mService.onStart();
@@ -204,6 +199,27 @@ public class TrustManagerServiceTest {
mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
}
+ private class MockInjector extends TrustManagerService.Injector {
+ MockInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ KeyStoreAuthorization getKeyStoreAuthorization() {
+ return mKeyStoreAuthorization;
+ }
+
+ @Override
+ Looper getLooper() {
+ return mHandlerThread.getLooper();
+ }
+ }
+
@After
public void tearDown() {
LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -371,14 +387,14 @@ public class TrustManagerServiceTest {
when(mWindowManager.isKeyguardLocked()).thenReturn(false);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
- verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
}
@@ -392,10 +408,10 @@ public class TrustManagerServiceTest {
setupMocksForProfile(/* unifiedChallenge= */ false);
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
- verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
}
@@ -572,11 +588,11 @@ public class TrustManagerServiceTest {
private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
when(mWindowManager.isKeyguardLocked()).thenReturn(false);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+ verify(mKeyStoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
eq(expectedWeakUnlockEnabled));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 7ecc7fd1b94b..29f3720a1828 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -235,12 +235,11 @@ public class WallpaperCropperTest {
int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
Point expectedCropSize = new Point(expectedWidth, 1000);
for (int mode: ALL_MODES) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, false, mode))
- .isEqualTo(leftOf(crop, expectedCropSize));
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, true, mode))
- .isEqualTo(rightOf(crop, expectedCropSize));
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
}
}
@@ -362,11 +361,13 @@ public class WallpaperCropperTest {
}
/**
- * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
- * no suggested crops are provided.
+ * Test that {@link WallpaperCropper#getCrop} uses the full image when no crops are provided.
+ * If the image has more width/height ratio than the screen, keep that width for parallax up
+ * to {@link WallpaperCropper#MAX_PARALLAX}. If the crop has less width/height ratio, remove the
+ * surplus height, on both sides to keep the wallpaper centered.
*/
@Test
- public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ public void testGetCrop_noSuggestedCrops() {
setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(800, 1000);
Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
@@ -374,9 +375,11 @@ public class WallpaperCropperTest {
List<Point> displaySizes = List.of(
new Point(500, 1000),
+ new Point(200, 1000),
new Point(1000, 500));
List<Point> expectedCropSizes = List.of(
- new Point(500, 1000),
+ new Point(Math.min(800, (int) (500 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
+ new Point(Math.min(800, (int) (200 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
new Point(800, 400));
for (int i = 0; i < displaySizes.size(); i++) {
@@ -450,7 +453,8 @@ public class WallpaperCropperTest {
/**
* Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
* crop only for the relative unfolded orientation, creates the folded crop at the center of the
- * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions, and
+ * then adds some width for parallax.
* <p>
* To simplify, in this test case all crops have the same size as the display (no zoom)
* and are at the center of the image.
@@ -468,6 +472,7 @@ public class WallpaperCropperTest {
int unfoldedTwo = getRotatedOrientation(unfoldedOne);
Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ List<Rect> unfoldedCrops = List.of(unfoldedCropOne, unfoldedCropTwo);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
suggestedCrops.put(unfoldedOne, unfoldedCropOne);
suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
@@ -476,15 +481,28 @@ public class WallpaperCropperTest {
int foldedTwo = getFoldedOrientation(unfoldedTwo);
Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ List<Point> foldedDisplays = List.of(foldedDisplayOne, foldedDisplayTwo);
for (boolean rtl : List.of(false, true)) {
- assertThat(mWallpaperCropper.getCrop(
- foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
- .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
-
- assertThat(mWallpaperCropper.getCrop(
- foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
- .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ for (int i = 0; i < 2; i++) {
+ Rect unfoldedCrop = unfoldedCrops.get(i);
+ Point foldedDisplay = foldedDisplays.get(i);
+ Rect expectedCrop = centerOf(unfoldedCrop, foldedDisplay);
+ int maxParallax = (int) (WallpaperCropper.MAX_PARALLAX * unfoldedCrop.width());
+
+ // the expected behaviour is that we add width for parallax until we reach
+ // either MAX_PARALLAX or the edge of the crop for the unfolded screen.
+ if (rtl) {
+ expectedCrop.left = Math.max(
+ unfoldedCrop.left, expectedCrop.left - maxParallax);
+ } else {
+ expectedCrop.right = Math.min(
+ unfoldedCrop.right, unfoldedCrop.right + maxParallax);
+ }
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(expectedCrop);
+ }
}
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 05d8a005d21e..c4561b16d73c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -87,7 +87,7 @@ public class BatteryStatsUserLifecycleTests {
final boolean[] userStopped = new boolean[1];
CountDownLatch stopUserLatch = new CountDownLatch(1);
- mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(mTestUserId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
userStopped[0] = true;
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
new file mode 100644
index 000000000000..f38723854d6b
--- /dev/null
+++ b/services/tests/selinux/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_foundation_security_rust_pkvm_",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_defaults {
+ name: "mockito_extended",
+ static_libs: [
+ "mockito-target-extended-minus-junit4",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+}
+
+android_test {
+ name: "SelinuxFrameworksTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+ defaults: [
+ "mockito_extended",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ "servicestests-core-utils",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.runner",
+ "services.core",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+}
diff --git a/services/tests/selinux/AndroidManifest.xml b/services/tests/selinux/AndroidManifest.xml
new file mode 100644
index 000000000000..9273795322bd
--- /dev/null
+++ b/services/tests/selinux/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.selinuxtests">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.selinuxtests"
+ android:label="Selinux Frameworks Tests" />
+</manifest> \ No newline at end of file
diff --git a/services/tests/selinux/AndroidTest.xml b/services/tests/selinux/AndroidTest.xml
new file mode 100644
index 000000000000..16d8e0709345
--- /dev/null
+++ b/services/tests/selinux/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Selinux Frameworks Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="SelinuxFrameworksTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="SelinuxFrameworksTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.selinuxtests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS b/services/tests/selinux/OWNERS
index 49a0934ec642..49a0934ec642 100644
--- a/services/tests/mockingservicestests/src/com/android/server/selinux/OWNERS
+++ b/services/tests/selinux/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java b/services/tests/selinux/src/com/android/server/selinux/RateLimiterTest.java
index 01c7fbe5bfe9..01c7fbe5bfe9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/RateLimiterTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
index b36c9bdaf456..b36c9bdaf456 100644
--- a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index 9758ea596335..4a70ad38a76f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -69,8 +69,7 @@ public class SelinuxAuditLogsCollectorTest {
// Ignore what was written in the event logs by previous tests.
mSelinuxAutidLogsCollector.mLastWrite = Instant.now();
- mMockitoSession =
- mockitoSession().initMocks(this).mockStatic(FrameworkStatsLog.class).startMocking();
+ mMockitoSession = mockitoSession().mockStatic(FrameworkStatsLog.class).startMocking();
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e0a99b016da9..e18909879934 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -3156,6 +3156,39 @@ public class AccountManagerServiceTest extends AndroidTestCase {
}
@SmallTest
+ public void testAccountRemovedBroadcastMarkedAccountAsVisibleTwice() throws Exception {
+ unlockSystemUser();
+
+ HashMap<String, Integer> visibility = new HashMap<>();
+ visibility.put("testpackage1", AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+
+ addAccountRemovedReceiver("testpackage1");
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage1");
+ mAms.addAccountExplicitlyWithVisibility(
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ /* password= */ "p11",
+ /* extras= */ null,
+ visibility,
+ /* callerPackage= */ null);
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_VISIBLE);
+
+ updateBroadcastCounters(3);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1); // visibility was not changed
+ assertEquals(mLoginAccountsChangedBroadcasts, 2);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+ }
+
+ @SmallTest
public void testRegisterAccountListenerCredentialsUpdate() throws Exception {
unlockSystemUser();
mAms.registerAccountListener(
@@ -3493,6 +3526,12 @@ public class AccountManagerServiceTest extends AndroidTestCase {
}
@Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn,
+ Context.BindServiceFlags flags, UserHandle user) {
+ return mTestContext.bindServiceAsUser(service, conn, flags, user);
+ }
+
+ @Override
public void unbindService(ServiceConnection conn) {
mTestContext.unbindService(conn);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0a2a855e9317..7c0dbf4889da 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -888,7 +888,7 @@ public class UserControllerTest {
int userId = -1;
assertThrows(IllegalArgumentException.class,
- () -> mUserController.stopUser(userId, /* force= */ true,
+ () -> mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null));
}
@@ -897,7 +897,7 @@ public class UserControllerTest {
public void testStopUser_systemUser() {
int userId = UserHandle.USER_SYSTEM;
- int r = mUserController.stopUser(userId, /* force= */ true,
+ int r = mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -909,7 +909,7 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, /* flags= */ 0);
mUserController.startUser(TEST_USER_ID1, USER_START_MODE_FOREGROUND);
- int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+ int r = mUserController.stopUser(TEST_USER_ID1,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -1338,7 +1338,7 @@ public class UserControllerTest {
private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
- int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+ int r = mUserController.stopUser(userId, /* allowDelayedLocking= */
allowDelayedLocking, null, keyEvictedCallback);
assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index f1c1dc365b90..59f4d56b44f1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -315,7 +316,7 @@ public class AudioDeviceBrokerTest {
mFakeBtDevice.getAddress()));
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
- eq(devState));
+ eq(devState), anyBoolean());
}
// metadata set
@@ -326,7 +327,7 @@ public class AudioDeviceBrokerTest {
mFakeBtDevice.getAddress()));
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
- any());
+ any(), anyBoolean());
}
} finally {
// reset the metadata device type
@@ -354,7 +355,7 @@ public class AudioDeviceBrokerTest {
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS).atLeast(1)).onUpdatedAdiDeviceState(
ArgumentMatchers.argThat(devState -> devState.getAudioDeviceCategory()
- == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER));
+ == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER), anyBoolean());
}
private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 74eb79d7554c..34092b6855b1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -68,7 +68,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import androidx.test.filters.SmallTest;
@@ -105,7 +105,7 @@ public class AuthSessionTest {
@Mock private IBiometricServiceReceiver mClientReceiver;
@Mock private IStatusBarService mStatusBarService;
@Mock private IBiometricSysuiReceiver mSysuiReceiver;
- @Mock private KeyStore mKeyStore;
+ @Mock private KeyStoreAuthorization mKeyStoreAuthorization;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
@Mock private BiometricCameraManager mBiometricCameraManager;
@@ -665,9 +665,10 @@ public class AuthSessionTest {
final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
checkDevicePolicyManager);
return new AuthSession(mContext, mBiometricContext, mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId,
- operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
- false /* debugEnabled */, mFingerprintSensorProps, mBiometricFrameworkStatsLogger);
+ mKeyStoreAuthorization, mRandom, mClientDeathReceiver, preAuthInfo, mToken,
+ requestId, operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE,
+ promptInfo, false /* debugEnabled */, mFingerprintSensorProps,
+ mBiometricFrameworkStatsLogger);
}
private PromptInfo createPromptInfo(@Authenticators.Types int authenticators) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a852677c2ed1..5fd29c2b0cb9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -82,8 +82,7 @@ import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -182,7 +181,7 @@ public class BiometricServiceTest {
private BiometricHandlerProvider mBiometricHandlerProvider;
@Mock
- private IKeystoreAuthorization mKeystoreAuthService;
+ private KeyStoreAuthorization mKeyStoreAuthorization;
@Mock
private IGateKeeperService mGateKeeperService;
@@ -207,7 +206,7 @@ public class BiometricServiceTest {
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
when(mInjector.getSettingObserver(any(), any(), any()))
.thenReturn(mock(BiometricService.SettingObserver.class));
- when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+ when(mInjector.getKeyStoreAuthorization()).thenReturn(mock(KeyStoreAuthorization.class));
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
when(mInjector.getBiometricStrengthController(any()))
.thenReturn(mock(BiometricStrengthController.class));
@@ -243,7 +242,7 @@ public class BiometricServiceTest {
mStatusBarService, null /* handler */,
mAuthSessionCoordinator);
when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
- when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+ when(mInjector.getKeyStoreAuthorization()).thenReturn(mKeyStoreAuthorization);
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
@@ -682,9 +681,9 @@ public class BiometricServiceTest {
waitForIdle();
// HAT sent to keystore
if (isStrongBiometric) {
- verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+ verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
} else {
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
}
// Send onAuthenticated to client
verify(mReceiver1).onAuthenticationSucceeded(
@@ -747,7 +746,7 @@ public class BiometricServiceTest {
waitForIdle();
// Waiting for SystemUI to send confirmation callback
assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
// SystemUI sends confirm, HAT is sent to keystore and client is notified.
mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
@@ -755,9 +754,9 @@ public class BiometricServiceTest {
null /* credentialAttestation */);
waitForIdle();
if (isStrongBiometric) {
- verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+ verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
} else {
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
}
verify(mReceiver1).onAuthenticationSucceeded(
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
@@ -1313,7 +1312,7 @@ public class BiometricServiceTest {
eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
assertNull(mBiometricService.mAuthSession);
}
@@ -1839,7 +1838,7 @@ public class BiometricServiceTest {
final long expectedResult = 31337L;
- when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+ when(mKeyStoreAuthorization.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
.thenReturn(expectedResult);
mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
@@ -1848,7 +1847,8 @@ public class BiometricServiceTest {
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
assertEquals(expectedResult, result);
- verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+ verify(mKeyStoreAuthorization).getLastAuthTime(eq(secureUserId),
+ eq(hardwareAuthenticators));
}
// Helper methods
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
index dae8f932cb91..09462290fb4f 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -1,23 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.contentcapture"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_contentcapture"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
index 32729a899a96..1ad7baa09529 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -1,23 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.contentprotection"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_contentprotection"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index fa892782f42e..44aa868716eb 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,6 +30,9 @@ import android.graphics.fonts.SystemFonts;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.system.Os;
import android.text.FontConfig;
import android.util.Xml;
@@ -38,8 +41,11 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.text.flags.Flags;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
@@ -69,6 +75,9 @@ public final class UpdatableFontDirTest {
private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
/**
* A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files,
* this test uses fake font files. A fake font file has its PostScript naem and revision as the
@@ -1097,6 +1106,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1116,6 +1126,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1135,6 +1146,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1154,6 +1166,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1173,6 +1186,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1192,6 +1206,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1211,6 +1226,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1230,6 +1246,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1249,6 +1266,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1268,6 +1286,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1287,6 +1306,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1306,6 +1326,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1325,6 +1346,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1344,6 +1366,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1363,6 +1386,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1382,6 +1406,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1401,6 +1426,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1420,6 +1446,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1439,6 +1466,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1458,6 +1486,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1477,6 +1506,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1497,6 +1527,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1517,6 +1548,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1537,6 +1569,7 @@ public final class UpdatableFontDirTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index dc8f934ff9e5..58f5bb3eb7d0 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -1,29 +1,11 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.location.contexthub."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ "name": "FrameworksServicesTests_contexthub_presubmit"
}
],
"postsubmit": [
{
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_contexthub_presubmit"
- },
- {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 41c4383a0bec..944c1df94b92 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -1,12 +1,7 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.om."
- }
- ]
+ "name": "FrameworksServicesTests_om"
},
{
"name": "PackageManagerServiceHostTests",
@@ -16,11 +11,5 @@
}
]
}
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_om"
- }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 06e7002924a7..2138da9478d7 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -1,17 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.os."
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_os"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index f4e724f493c5..861562d11f10 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -21,7 +21,7 @@
"postsubmit": [
{
// Presubmit is intentional here while testing with SLO checker.
- // b/331020193, Move to presubmit early april 2024
+ // Tests are flaky, waiting to bypass.
"name": "FrameworksServicesTests_pm_presubmit"
},
{
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index 43bf53714cba..72caae3694de 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -242,7 +242,7 @@ public class UserLifecycleStressTest {
private void stopUser(int userId) throws RemoteException, InterruptedException {
runWithLatch("stop user", countDownLatch -> {
ActivityManager.getService()
- .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
+ .stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) {
countDownLatch.countDown();
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 7e7393c3a822..eb7453d5b86a 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -1,21 +1,7 @@
{
- "presubmit": [
- {
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.recoverysystem."
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_recoverysystem"
- }
- ]
-} \ No newline at end of file
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests_recoverysystem"
+ }
+ ]
+}
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 f6fa487f21ae..610627886c1b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -35,6 +35,7 @@ import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
@@ -112,6 +113,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
+import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -6047,7 +6049,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
verify(handler, timeout(300).times(0)).scheduleSendRankingUpdate();
- verify(handler, times(1)).scheduleCancelNotification(any());
+ verify(handler, times(1)).scheduleCancelNotification(any(), eq(0));
}
@Test
@@ -6306,7 +6308,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.CancelNotificationRunnable.class);
verify(handler, times(1)).scheduleCancelNotification(
- captor.capture());
+ captor.capture(), eq(0));
// Run the runnable given to the cancel notification, and see if it logs properly
NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue();
@@ -8650,34 +8652,128 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testOnNotificationActionClickLifetimeExtendedEnds() {
+ public void testActionClickLifetimeExtendedCancel() throws Exception {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+
final Notification.Action action =
new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
- final boolean generatedByAssistant = false;
// Creates a notification marked as being lifetime extended.
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(r);
+
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(1);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+
// Call on action click.
NotificationVisibility notificationVisibility =
NotificationVisibility.obtain(r.getKey(), 1, 2, true);
mService.mNotificationDelegate.onNotificationActionClick(
10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
/*generatedByAssistant=*/false);
- // The flag is removed, so the notification is no longer lifetime extended.
+
+ // Lifetime extended flag persists.
assertThat(r.getSbn().getNotification().flags
- & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0);
- // The record is sent out without the flag.
- ArgumentCaptor<NotificationRecord> captor =
- ArgumentCaptor.forClass(NotificationRecord.class);
- verify(mAssistants, times(1)).notifyAssistantActionClicked(
- captor.capture(), eq(action), eq(generatedByAssistant));
- assertThat(captor.getValue().getNotification().flags
- & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0);
+ mTestableLooper.moveTimeForward(210);
+ waitForIdle();
+ verify(mWorkerHandler, times(1))
+ .scheduleCancelNotification(
+ any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
+
+ // Check that the cancelation occurred and the notification is gone.
+ notifs = mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(0);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testActionClickLifetimeExtendedCancel_PreventByNoDismiss() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+
+ final Notification.Action action =
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+
+ // Creates a notification marked as being lifetime extended.
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ // Make the notification non-dismissable
+ r.getSbn().getNotification().flags |= FLAG_NO_DISMISS;
+ mService.addNotification(r);
+
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(1);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+
+ // Call on action click.
+ NotificationVisibility notificationVisibility =
+ NotificationVisibility.obtain(r.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationActionClick(
+ 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
+ /*generatedByAssistant=*/false);
+
+ // Lifetime extended flag persists.
+ assertThat(r.getSbn().getNotification().flags
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isGreaterThan(0);
+
+ mTestableLooper.moveTimeForward(210);
+ waitForIdle();
+ verify(mWorkerHandler, times(1))
+ .scheduleCancelNotification(
+ any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
+
+ // The cancellation is dropped and the notification is still present, with the update.
+ notifs = mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(1);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testUpdateOnActionClickDropsLifetimeExtendedCancel() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
+
+ final Notification.Action action =
+ new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
+ mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+
+ // Creates a notification marked as being lifetime extended.
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+ mService.addNotification(r);
+
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(1);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+
+ // Call on action click.
+ NotificationVisibility notificationVisibility =
+ NotificationVisibility.obtain(r.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationActionClick(
+ 10, 10, r.getKey(), /*actionIndex=*/2, action, notificationVisibility,
+ /*generatedByAssistant=*/false);
+
+ // The "app" sends an update of the notification in response.
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getSbn().getNotification(), r.getSbn().getUserId());
+
+ mTestableLooper.moveTimeForward(210);
+ waitForIdle();
+ verify(mWorkerHandler, times(1))
+ .scheduleCancelNotification(
+ any(NotificationManagerService.CancelNotificationRunnable.class), eq(200));
+
+ // The cancellation is dropped and the notification is still present, with the update.
+ notifs = mBinderService.getActiveNotifications(mPkg);
+ assertThat(notifs.length).isEqualTo(1);
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
}
@Test
@@ -8705,6 +8801,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mNotificationRecordLogger.event(0));
}
+
@Test
public void testLogSmartSuggestionsVisible_triggerOnExpandAndVisible() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -14898,6 +14995,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.isEqualTo(USAGE_NOTIFICATION);
}
+ @Test
+ @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ public void testFixNotification_missingTtl() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+
+ mService.fixNotification(n, mPkg, "tag", 0, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
+
+ assertThat(n.getTimeoutAfter()).isEqualTo(NOTIFICATION_TTL);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ public void testFixNotification_doesNotOverwriteTtl() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setTimeoutAfter(20)
+ .build();
+
+ mService.fixNotification(n, mPkg, "tag", 0, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
+
+ assertThat(n.getTimeoutAfter()).isEqualTo(20);
+ }
+
private NotificationRecord createAndPostCallStyleNotification(String packageName,
UserHandle userHandle, String testName) throws Exception {
Person person = new Person.Builder().setName("caller").build();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 52df010bd588..eac99298559e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -85,7 +85,6 @@ import android.os.VibratorInfo;
import android.os.test.TestLooper;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -743,15 +742,8 @@ class TestPhoneWindowManager {
void assertSwitchKeyboardLayout(int direction, int displayId) {
mTestLooper.dispatchAll();
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
- eq(displayId), eq(mImeTargetWindowToken));
- verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
- } else {
- verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
- verify(mInputMethodManagerInternal, never())
- .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
- }
+ verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+ eq(displayId), eq(mImeTargetWindowToken));
}
void assertTakeBugreport() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 13550923cf3d..10eae577f706 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -46,6 +46,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
+import static android.server.wm.ActivityManagerTestBase.isTablet;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,6 +76,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -122,6 +124,7 @@ import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
import com.android.server.wm.utils.MockTracker;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -1295,6 +1298,12 @@ public class ActivityStarterTests extends WindowTestsBase {
*/
@Test
public void testDeliverIntentToTopActivityOfNonTopDisplay() {
+ // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with
+ // desktop windowing mode
+ // Ignore test if desktop windowing is enabled on tablets as legacy multi-display
+ // behaviour will not be respected
+ assumeFalse(Flags.enableDesktopWindowingMode() && isTablet());
+
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 5aa4ba3e1d42..695faa525dd6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -504,22 +504,37 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
- + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: null; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+ + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: false; "
+ + "autoOptInReason: notPendingIntent; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: null; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_BAL; "
+ + "resultIfPiSenderAllowsBal: null");
}
@Test
@@ -588,21 +603,36 @@ public class BackgroundActivityStartControllerTests {
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
- + "null; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.NONE; "
+ + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: true; "
+ + "autoOptInReason: null; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: PendingIntentRecord; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_FGS; "
+ + "resultIfPiSenderAllowsBal: null");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index b11f9b2306df..073b55165c9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
+import android.os.Message;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -60,6 +61,8 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
private int mColorMode;
private int mLogicalDensityDpi;
+ private final Message mScreenUnblocker = mock(Message.class);
+
@Override
protected void onBeforeSystemServicesCreated() {
// Set other flags to their default values
@@ -73,12 +76,11 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
doReturn(true).when(mDisplayContent).getLastHasContent();
mockTransitionsController(/* enabled= */ true);
mockRemoteDisplayChangeController();
+ performInitialDisplayUpdate();
}
@Test
public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
- performInitialDisplayUpdate();
-
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -107,8 +109,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() {
- performInitialDisplayUpdate();
-
// Update only color mode (non-deferrable field) and keep the same unique id
mUniqueId = "initial_unique_id";
mColorMode = 123;
@@ -121,8 +121,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() {
- performInitialDisplayUpdate();
-
// Update only color mode (non-deferrable field) and keep the same unique id
mUniqueId = "initial_unique_id";
mColorMode = 123;
@@ -163,7 +161,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
- performInitialDisplayUpdate();
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -181,7 +178,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
- performInitialDisplayUpdate();
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -212,6 +208,51 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2");
}
+ @Test
+ public void testWaitForTransition_displaySwitching_waitsForTransitionToBeStarted() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+ assertThat(willWait).isTrue();
+ mUniqueId = "new";
+ mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+ when(mDisplayContent.mTransitionController.inTransition()).thenReturn(true);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+
+ // Verify that screen is not unblocked yet as the start transaction hasn't been presented
+ verify(mScreenUnblocker, never()).sendToTarget();
+
+ when(mDisplayContent.mTransitionController.inTransition()).thenReturn(false);
+ final Transition transition = captureRequestedTransition().getValue();
+ makeTransitionTransactionCompleted(transition);
+
+ // Verify that screen is unblocked as start transaction of the transition
+ // has been completed
+ verify(mScreenUnblocker).sendToTarget();
+ }
+
+ @Test
+ public void testWaitForTransition_displayNotSwitching_doesNotWait() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ false);
+
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+
+ assertThat(willWait).isFalse();
+ verify(mScreenUnblocker, never()).sendToTarget();
+ }
+
+ @Test
+ public void testWaitForTransition_displayIsSwitchingButFlagDisabled_doesNotWait() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
+
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+
+ assertThat(willWait).isFalse();
+ verify(mScreenUnblocker, never()).sendToTarget();
+ }
+
private void mockTransitionsController(boolean enabled) {
spyOn(mDisplayContent.mTransitionController);
when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
@@ -233,6 +274,23 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
return callbackCaptor;
}
+ private ArgumentCaptor<Transition> captureRequestedTransition() {
+ ArgumentCaptor<Transition> callbackCaptor =
+ ArgumentCaptor.forClass(Transition.class);
+ verify(mDisplayContent.mTransitionController, atLeast(1))
+ .requestStartTransition(callbackCaptor.capture(), any(), any(), any());
+ return callbackCaptor;
+ }
+
+ private void makeTransitionTransactionCompleted(Transition transition) {
+ if (transition.mTransactionCompletedListeners != null) {
+ for (int i = 0; i < transition.mTransactionCompletedListeners.size(); i++) {
+ final Runnable listener = transition.mTransactionCompletedListeners.get(i);
+ listener.run();
+ }
+ }
+ }
+
private void performInitialDisplayUpdate() {
mUniqueId = "initial_unique_id";
mColorMode = 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 1233686a4b48..00a8842c358e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -167,6 +167,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
+ public void onDisplaySwitchStart(int displayId) {
+ }
+
+ @Override
public boolean okToAnimate(boolean ignoreScreenOn) {
return mOkToAnimate;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 848702103e65..ec2c968a8a0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -101,6 +101,7 @@ import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerGlobal;
import android.window.ActivityWindowInfo;
import android.window.ClientWindowFrames;
import android.window.InputTransferToken;
@@ -244,15 +245,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- public void testDismissKeyguardCanWakeUp() {
- doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString());
- doReturn(true).when(mWm.mAtmService.mKeyguardController).isShowingDream();
- doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
- mWm.dismissKeyguard(null, "test-dismiss-keyguard");
- verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
- }
-
- @Test
public void testTrackOverlayWindow() {
final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
"pkgName", "processName", 1000 /* pid */, Process.SYSTEM_UID);
@@ -556,6 +548,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
.hasListener(eq(windowContextToken));
doReturn(TYPE_INPUT_METHOD).when(mWm.mWindowContextListenerController)
.getWindowType(eq(windowContextToken));
+ doReturn(true).when(mWm.mUmInternal).isUserVisible(anyInt(), anyInt());
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
UserHandle.USER_SYSTEM, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
@@ -1308,6 +1301,24 @@ public class WindowManagerServiceTests extends WindowTestsBase {
assertEquals(activityWindowInfo2, activityWindowInfo3);
}
+ @Test
+ public void testAddOverlayWindowToUnassignedDisplay_notAllowed() {
+ int uid = 100000; // uid for non-system user
+ Session session = createTestSession(mAtm, 1234 /* pid */, uid);
+ DisplayContent dc = createNewDisplay();
+ int displayId = dc.getDisplayId();
+ int userId = UserHandle.getUserId(uid);
+ doReturn(false).when(mWm.mUmInternal).isUserVisible(eq(userId), eq(displayId));
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ LayoutParams.TYPE_APPLICATION_OVERLAY);
+
+ int result = mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, displayId,
+ userId, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
+ new InsetsSourceControl.Array(), new Rect(), new float[1]);
+
+ assertThat(result).isEqualTo(WindowManagerGlobal.ADD_INVALID_DISPLAY);
+ }
+
class TestResultReceiver implements IResultReceiver {
public android.os.Bundle resultData;
private final IBinder mBinder = mock(IBinder.class);
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index e8ffe541d641..e00627e4f0b5 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -44,6 +44,7 @@ java_library_static {
aconfig_declarations {
name: "usb_flags",
package: "com.android.server.usb.flags",
+ container: "system",
srcs: ["**/usb_flags.aconfig"],
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 2da352d43290..9470c0a944c2 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -60,6 +60,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
@@ -222,6 +223,21 @@ public class UsbService extends IUsbManager.Stub {
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
}
+ // Ideally we should use the injector pattern so we wouldn't need this constructor for test
+ @VisibleForTesting
+ UsbService(Context context,
+ UsbPortManager usbPortManager,
+ UsbAlsaManager usbAlsaManager,
+ UserManager userManager,
+ UsbSettingsManager usbSettingsManager) {
+ mContext = context;
+ mPortManager = usbPortManager;
+ mAlsaManager = usbAlsaManager;
+ mUserManager = userManager;
+ mSettingsManager = usbSettingsManager;
+ mPermissionManager = new UsbPermissionManager(context, this);
+ }
+
/**
* Set new {@link #mCurrentUserId} and propagate it to other modules.
*
@@ -886,7 +902,16 @@ public class UsbService extends IUsbManager.Stub {
@Override
public boolean enableUsbData(String portId, boolean enable, int operationId,
- IUsbOperationInternal callback) {
+ IUsbOperationInternal callback) {
+ return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+ }
+
+ /**
+ * Internal function abstracted for testing with callerUid
+ */
+ @VisibleForTesting
+ boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
+ IUsbOperationInternal callback, int callerUid) {
Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -894,7 +919,14 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, enable, Binder.getCallingUid())) return false;
+ if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+ try {
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e);
+ }
+ return false;
+ }
}
final long ident = Binder.clearCallingIdentity();
@@ -943,15 +975,35 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
- IUsbOperationInternal callback) {
+ IUsbOperationInternal callback) {
+ enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+ }
+
+ /**
+ * Internal function abstracted for testing with callerUid
+ */
+ @VisibleForTesting
+ void enableUsbDataWhileDockedInternal(String portId, int operationId,
+ IUsbOperationInternal callback, int callerUid) {
Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback,
"enableUsbDataWhileDocked: callback must not be null. opId:"
+ operationId);
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
+ if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+ try {
+ callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "enableUsbDataWhileDocked: Failed to call onOperationComplete", e);
+ }
+ return;
+ }
+ }
+
final long ident = Binder.clearCallingIdentity();
- boolean wait;
try {
if (mPortManager != null) {
mPortManager.enableUsbDataWhileDocked(portId, operationId, callback, null);
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index ea6e50221cd0..a7c5ddb115b8 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.usb.flags"
+container: "system"
flag {
name: "allow_restriction_of_overlay_activities"
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 86eed2f6a090..ec60c676d078 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -793,7 +793,7 @@ public final class TelephonyPermissions {
if (isGranted) return;
if (allowCarrierPrivilegeOnAnySub) {
- if (checkCarrierPrivilegeForAnySubId(context, Binder.getCallingUid())) return;
+ if (checkCarrierPrivilegeForAnySubId(context, uid)) return;
} else {
if (checkCarrierPrivilegeForSubId(context, subId)) return;
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index a63db88cb614..7b5b07c0fbf6 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony.util;
import static android.telephony.Annotation.DataState;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +39,7 @@ import android.provider.Telephony.Carriers.EditStatus;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
@@ -48,6 +51,8 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* This class provides various util functions
@@ -342,4 +347,31 @@ public final class TelephonyUtils {
return false;
}
+
+ /**
+ * @param plmn target plmn for validation.
+ * @return {@code true} if the target plmn is valid {@code false} otherwise.
+ */
+ public static boolean isValidPlmn(@Nullable String plmn) {
+ if (TextUtils.isEmpty(plmn)) {
+ return false;
+ }
+ Pattern pattern = Pattern.compile("^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
+ Matcher matcher = pattern.matcher(plmn);
+ if (!matcher.matches()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param serviceType target serviceType for validation.
+ * @return {@code true} if the target serviceType is valid {@code false} otherwise.
+ */
+ public static boolean isValidService(int serviceType) {
+ if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebdd556428b5..7db41804067b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9890,6 +9890,60 @@ public class CarrierConfigManager {
"satellite_entitlement_app_name_string";
/**
+ * URL to redirect user to get more information about the carrier support for satellite.
+ *
+ * The default value is empty string.
+ *
+ * @hide
+ */
+ public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
+ "satellite_information_redirect_url_string";
+ /**
+ * Indicate whether a carrier supports emergency messaging. When this config is {@code false},
+ * emergency call to satellite T911 handover will be disabled.
+ *
+ * This will need agreement with carriers before enabling this flag.
+ *
+ * The default value is false.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
+ "emergency_messaging_supported_bool";
+
+ /**
+ * An integer key holds the timeout duration in milliseconds used to determine whether to hand
+ * over an emergency call to satellite T911.
+ *
+ * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+ * and cellular service is not available. When the timer expires,
+ * {@link com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender} will send the
+ * event {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then
+ * prompt user to switch to using satellite emergency messaging.
+ *
+ * The default value is 30 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
+ "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+
+ /**
+ * An int array that contains default capabilities for carrier enabled satellite roaming.
+ * If any PLMN is provided from the entitlement server, and it is not listed in
+ * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}, default capabilities
+ * will be used instead.
+ * <p>
+ * The default capabilities are
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
+ "carrier_roaming_satellite_default_services_int_array";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -11034,7 +11088,16 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 7);
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
+ sDefaults.putString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, "");
+ sDefaults.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+ new int[] {
+ NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+ NetworkRegistrationInfo.SERVICE_TYPE_MMS
+ });
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+ sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
+ sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+ (int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c5f2d42389e5..ba7ba53272f1 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3137,7 +3137,7 @@ public class SubscriptionManager {
if (useRootLocale) {
configurationKey.setLocale(Locale.ROOT);
}
- cacheKey = Pair.create(context.getPackageName(), configurationKey);
+ cacheKey = Pair.create(context.getPackageName() + ", subid=" + subId, configurationKey);
synchronized (sResourcesCache) {
Resources cached = sResourcesCache.get(cacheKey);
if (cached != null) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 06fc3c631eba..579fda320e9a 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -26,11 +26,13 @@ oneway interface ISatelliteTransmissionUpdateCallback {
/**
* Called when satellite datagram send state changed.
*
+ * @param datagramType The datagram type of currently being sent.
* @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+ void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
+ int errorCode);
/**
* Called when satellite datagram receive state changed.
@@ -39,7 +41,7 @@ oneway interface ISatelliteTransmissionUpdateCallback {
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
+ void onReceiveDatagramStateChanged(int state, int receivePendingCount, int errorCode);
/**
* Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20b24b9db6f4..87bb0f05c742 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -992,12 +992,19 @@ public final class SatelliteManager {
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+ /**
+ * This type of datagram is used to keep the device in satellite connected state or check if
+ * there is any incoming message.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
- DATAGRAM_TYPE_LOCATION_SHARING
+ DATAGRAM_TYPE_LOCATION_SHARING,
+ DATAGRAM_TYPE_KEEP_ALIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
@@ -1077,8 +1084,13 @@ public final class SatelliteManager {
}
@Override
- public void onSendDatagramStateChanged(int state, int sendPendingCount,
- int errorCode) {
+ public void onSendDatagramStateChanged(int datagramType, int state,
+ int sendPendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSendDatagramStateChanged(datagramType,
+ state, sendPendingCount, errorCode)));
+
+ // For backward compatibility
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> callback.onSendDatagramStateChanged(
state, sendPendingCount, errorCode)));
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index e02097019c4c..d8bd66284fb0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -52,6 +52,19 @@ public interface SatelliteTransmissionUpdateCallback {
@SatelliteManager.SatelliteResult int errorCode);
/**
+ * Called when satellite datagram send state changed.
+ *
+ * @param datagramType The datagram type of currently being sent.
+ * @param state The new send datagram transfer state.
+ * @param sendPendingCount The number of datagrams that are currently being sent.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ *
+ * @hide
+ */
+ void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+ @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+ @SatelliteManager.SatelliteResult int errorCode);
+ /**
* Called when satellite datagram receive state changed.
*
* @param state The new receive datagram transfer state.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 36485c6b6fb5..16983a0dbca1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -83,6 +83,9 @@ oneway interface ISatellite {
*
* @param enableSatellite True to enable the satellite modem and false to disable.
* @param enableDemoMode True to enable demo mode and false to disable.
+ * @param isEmergency To specify the satellite is enabled for emergency session and false for
+ * non emergency session. Note: it is possible that a emergency session started get converted
+ * to a non emergency session and vice versa.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned:
@@ -96,7 +99,7 @@ oneway interface ISatellite {
* SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
*/
void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
- in IIntegerConsumer resultCallback);
+ in boolean isEmergency, in IIntegerConsumer resultCallback);
/**
* Request to get whether the satellite modem is enabled.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index b7dc79ff7283..a62363335fb9 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -90,11 +90,11 @@ public class SatelliteImplBase extends SatelliteService {
@Override
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
- IIntegerConsumer resultCallback) throws RemoteException {
+ boolean isEmergency, IIntegerConsumer resultCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
.requestSatelliteEnabled(
- enableSatellite, enableDemoMode, resultCallback),
+ enableSatellite, enableDemoMode, isEmergency, resultCallback),
"requestSatelliteEnabled");
}
@@ -337,6 +337,9 @@ public class SatelliteImplBase extends SatelliteService {
*
* @param enableSatellite True to enable the satellite modem and false to disable.
* @param enableDemoMode True to enable demo mode and false to disable.
+ * @param isEmergency To specify the satellite is enabled for emergency session and false for
+ * non emergency session. Note: it is possible that a emergency session started get converted
+ * to a non emergency session and vice versa.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned:
@@ -350,7 +353,7 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
*/
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
- @NonNull IIntegerConsumer resultCallback) {
+ boolean isEmergency, @NonNull IIntegerConsumer resultCallback) {
// stub implementation
}
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 511c94849681..13902184ac6e 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -17,8 +17,12 @@
package com.android.server.wm.flicker.activityembedding.rotation
import android.platform.test.annotations.Presubmit
+import android.tools.Position
+import android.tools.datatypes.Rect
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.Condition
+import android.tools.traces.DeviceStateDump
import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.setRotation
@@ -30,7 +34,14 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin
override val transition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions { this.setRotation(flicker.scenario.endRotation) }
+ transitions {
+ this.setRotation(flicker.scenario.endRotation)
+ if (!flicker.scenario.isTablet) {
+ wmHelper.StateSyncBuilder()
+ .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
+ .waitForAndVerify()
+ }
+ }
}
/** {@inheritDoc} */
@@ -76,4 +87,37 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddin
appLayerRotates_StartingPos()
appLayerRotates_EndingPos()
}
+
+ private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> {
+ return Condition("navBarPosition") { dump ->
+ val display =
+ dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id }
+ ?: error("There is no display!")
+ val displayArea = display.layerStackSpace
+ val navBarPosition = display.navBarPosition(isGesturalNavigation)
+ val navBarRegion = dump.layerState
+ .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)
+ ?.visibleRegion?.bounds ?: Rect.EMPTY
+
+ when (navBarPosition) {
+ Position.TOP ->
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.BOTTOM ->
+ navBarRegion.bottom == displayArea.bottom &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.LEFT ->
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ Position.RIGHT ->
+ navBarRegion.right == displayArea.right &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ else -> error("Unknown position $navBarPosition")
+ }
+ }
+ }
}
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index 8e210d455591..f1df8a68fb63 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -123,7 +123,9 @@ class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: LegacyFlicker
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest(bugId = 227143265)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
@FlakyTest(bugId = 278227468)
@Test
diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md
index 6b28fdf8a8ef..7429250f5cc0 100644
--- a/tests/FlickerTests/README.md
+++ b/tests/FlickerTests/README.md
@@ -7,82 +7,17 @@ The tests are organized in packages according to the transitions they test (e.g.
## Adding a Test
-By default tests should inherit from `RotationTestBase` or `NonRotationTestBase` and must override the variable `transitionToRun` (Kotlin) or the function `getTransitionToRun()` (Java).
-Only tests that are not supported by these classes should inherit directly from the `FlickerTestBase` class.
+By default, tests should inherit from `TestBase` and override the variable `transition` (Kotlin) or the function `getTransition()` (Java).
-### Rotation animations and transitions
+Inheriting from this class ensures the common assertions will be executed, namely:
-Tests that rotate the device should inherit from `RotationTestBase`.
-Tests that inherit from the class automatically receive start and end rotation values.
-Moreover, these tests inherit the following checks:
* all regions on the screen are covered
* status bar is always visible
-* status bar rotates
+* status bar is at the correct position at the start and end of the transition
* nav bar is always visible
-* nav bar is rotates
+* nav bar is at the correct position at the start and end of the transition
The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-### Non-Rotation animations and transitions
+For more examples of how a test looks like check `ChangeAppRotationTest` within the `Rotation` subdirectory.
-`NonRotationTestBase` was created to make it easier to write tests that do not involve rotation (e.g., `Pip`, `split screen` or `IME`).
-Tests that inherit from the class are automatically executed twice: once in portrait and once in landscape mode and the assertions are checked independently.
-Moreover, these tests inherit the following checks:
-* all regions on the screen are covered
-* status bar is always visible
-* nav bar is always visible
-
-The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-
-### Exceptional cases
-
-Tests that rotate the device should inherit from `RotationTestBase`.
-This class allows the test to be freely configured and does not provide any assertions.
-
-
-### Example
-
-Start by defining common or error prone transitions using `TransitionRunner`.
-```kotlin
-@LargeTest
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MyTest(
- beginRotationName: String,
- beginRotation: Int
-) : NonRotationTestBase(beginRotationName, beginRotation) {
- init {
- mTestApp = MyAppHelper(InstrumentationRegistry.getInstrumentation())
- }
-
- override val transitionToRun: TransitionRunner
- get() = TransitionRunner.newBuilder()
- .withTag("myTest")
- .recordAllRuns()
- .runBefore { device.pressHome() }
- .runBefore { device.waitForIdle() }
- .run { testApp.open() }
- .runAfter{ testApp.exit() }
- .repeat(2)
- .includeJankyRuns()
- .build()
-
- @Test
- fun myWMTest() {
- checkResults {
- WmTraceSubject.assertThat(it)
- .showsAppWindow(MyTestApp)
- .forAllEntries()
- }
- }
-
- @Test
- fun mySFTest() {
- checkResults {
- LayersTraceSubject.assertThat(it)
- .showsLayer(MyTestApp)
- .forAllEntries()
- }
- }
-}
-```
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index e60764f137af..80282c309320 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -33,7 +33,6 @@ import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
-import android.provider.Settings
import android.util.proto.ProtoOutputStream
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
@@ -47,9 +46,7 @@ import com.android.test.input.R
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
-import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -234,631 +231,326 @@ class KeyboardLayoutManagerTests {
}
@Test
- fun testDefaultUi_getKeyboardLayouts() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "Default UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayouts() {
+ val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+ assertNotEquals(
+ "Keyboard layout API should not return empty array",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue(
+ "Keyboard layout API should provide English(US) layout",
+ hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
}
@Test
- fun testNewUi_getKeyboardLayouts() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayout() {
+ val keyboardLayout =
+ keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+ assertEquals("getKeyboardLayout API should return correct Layout from " +
+ "available layouts",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ keyboardLayout!!.descriptor
+ )
}
@Test
- fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
- assertNotEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
+ fun testGetSetKeyboardLayoutForInputDevice_withImeInfo() {
+ val imeSubtype = createImeSubtype()
- val vendorSpecificKeyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
- vendorSpecificKeyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
- "specific layout",
- 1,
- vendorSpecificKeyboardLayouts.size
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
- "layout",
- VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
- vendorSpecificKeyboardLayouts[0].descriptor
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ var result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- }
- }
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the set layout",
+ ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
- @Test
- fun testNewUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ // This should replace previously set layout
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- }
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the last set layout",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
}
@Test
- fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(false).use {
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
- "nothing was set",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- val keyboardLayout =
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
- "layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout
+ fun testGetKeyboardLayoutListForInputDevice() {
+ // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+ var keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi-Latn")
+ )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for hi-Latn",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for hi-Latn",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
- }
- }
+ )
- @Test
- fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
+ // Check Layouts for "hi" which by default uses 'Deva' script.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi")
)
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
- "even after setCurrentKeyboardLayoutForInputDevice",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
+ assertEquals("getKeyboardLayoutListForInputDevice API should return empty " +
+ "list if no supported layouts available",
+ 0,
+ keyboardLayouts.size
+ )
- @Test
- fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+ // If user manually selected some layout, always provide it in the layout list
+ val imeSubtype = createImeSubtypeForLanguageTag("hi")
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ imeSubtype
)
-
- val keyboardLayouts =
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
- "layout",
+ assertEquals("getKeyboardLayoutListForInputDevice API should return user " +
+ "selected layout even if the script is incompatible with IME",
1,
- keyboardLayouts.size
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayouts[0]
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout (Auto select the first enabled layout)",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
- "the enabled layout is removed",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
-
- assertEquals(
- "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
- "an empty array",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_switchKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-
- // Throws null pointer because trying to show toast using TestLooper
- assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
- assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(UK) layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_switchKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
- testLooper.dispatchAll()
-
- assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
- "null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testNewUi_getKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
- NewSettingsApiFlag(false).use {
- val imeSubtype = createImeSubtype()
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutForInputDevice API should always return " +
- "KeyboardLayoutSelectionResult.FAILED",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
- NewSettingsApiFlag(true).use {
- val imeSubtype = createImeSubtype()
-
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- var result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
-
- // This should replace previously set layout
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtype()
- )
- assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
- "return empty array",
- 0,
- keyboardLayouts.size
- )
- }
- }
+ keyboardLayouts.size
+ )
- @Test
- fun testNewUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(true).use {
- // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
- var keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // Special case Japanese: UScript ignores provided script code for certain language tags
+ // Should manually match provided script codes and then rely on Uscript to derive
+ // script from language tags and match those.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi-Latn")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for hi-Latn",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ createImeSubtypeForLanguageTag("ja-Latn-JP")
)
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for hi-Latn",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code for ja-Latn-JP",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for ja-Latn-JP",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for ja-Latn-JP",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
+ )
- // Check Layouts for "hi" which by default uses 'Deva' script.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // If script code not explicitly provided for Japanese should rely on Uscript to find
+ // derived script code and hence no suitable layout will be found.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi")
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
- "list if no supported layouts available",
- 0,
- keyboardLayouts.size
- )
-
- // If user manually selected some layout, always provide it in the layout list
- val imeSubtype = createImeSubtypeForLanguageTag("hi")
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- imeSubtype
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
- "selected layout even if the script is incompatible with IME",
- 1,
- keyboardLayouts.size
- )
-
- // Special case Japanese: UScript ignores provided script code for certain language tags
- // Should manually match provided script codes and then rely on Uscript to derive
- // script from language tags and match those.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-Latn-JP")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code for ja-Latn-JP",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for ja-Latn-JP",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for ja-Latn-JP",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
- )
-
- // If script code not explicitly provided for Japanese should rely on Uscript to find
- // derived script code and hence no suitable layout will be found.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-JP")
- )
- assertEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
- "supported layouts with matching script code for ja-JP",
- 0,
- keyboardLayouts.size
- )
-
- // If IME doesn't have a corresponding language tag, then should show all available
- // layouts no matter the script code.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, null
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
- "language tag or subtype not provided",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
- "layouts if language tag or subtype not provided",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ createImeSubtypeForLanguageTag("ja-JP")
)
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
- "layouts if language tag or subtype not provided",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_russian")
- )
- )
- }
- }
+ assertEquals(
+ "getKeyboardLayoutListForInputDevice API should return empty list of " +
+ "supported layouts with matching script code for ja-JP",
+ 0,
+ keyboardLayouts.size
+ )
- @Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-US"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-GB"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("de"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("fr-FR"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("ru"),
+ // If IME doesn't have a corresponding language tag, then should show all available
+ // layouts no matter the script code.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, null
+ )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return all layouts if" +
+ "language tag or subtype not provided",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Latin " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(
+ keyboardLayouts,
createLayoutDescriptor("keyboard_layout_russian")
)
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("it")
- )
- )
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
- "available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("en-Deva")
- )
- )
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Try to match layout type even if country doesn't match
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Choose layout based on layout type priority, if layout type is not provided by IME
- // (Qwerty > Dvorak > Extended)
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- // Wrong layout type should match with language if provided layout type not available
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
- createLayoutDescriptor("keyboard_layout_russian_qwerty")
- )
- // If layout type is empty then prioritize KCM with empty layout type
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-US"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-GB"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("de"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("fr-FR"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("ru"),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("it")
)
- assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
"KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
"available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
- )
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("en-Deva")
)
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
- // Should return English dvorak even if IME current layout is French, since HW says the
- // keyboard is a Dvorak keyboard
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Try to match layout type even if country doesn't match
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Choose layout based on layout type priority, if layout type is not provided by IME
+ // (Qwerty > Dvorak > Extended)
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ // Wrong layout type should match with language if provided layout type not available
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+ createLayoutDescriptor("keyboard_layout_russian_qwerty")
+ )
+ // If layout type is empty then prioritize KCM with empty layout type
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals("getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
)
+ )
+ }
- // Back to back changing HW keyboards with same product and vendor ID but different
- // language and layout type should configure the layouts correctly.
- assertCorrectLayout(
- englishQwertyKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us")
- )
+ @Test
+ fun testGetDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+ val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+ // Should return English dvorak even if IME current layout is French, since HW says the
+ // keyboard is a Dvorak keyboard
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
- // Fallback to IME information if the HW provided layout script is incompatible with the
- // provided IME subtype
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
- )
- }
+ // Back to back changing HW keyboards with same product and vendor ID but different
+ // language and layout type should configure the layouts correctly.
+ assertCorrectLayout(
+ englishQwertyKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us")
+ )
+
+ // Fallback to IME information if the HW provided layout script is incompatible with the
+ // provided IME subtype
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
}
@Test
@@ -867,27 +559,25 @@ class KeyboardLayoutManagerTests {
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- GERMAN_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ GERMAN_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -897,27 +587,25 @@ class KeyboardLayoutManagerTests {
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- "en",
- LAYOUT_TYPE_QWERTY,
- ENGLISH_US_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- )
- ),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ "en",
+ LAYOUT_TYPE_QWERTY,
+ ENGLISH_US_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
+ )
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -925,27 +613,25 @@ class KeyboardLayoutManagerTests {
fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- "Default",
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ "Default",
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -953,19 +639,17 @@ class KeyboardLayoutManagerTests {
fun testConfigurationNotLogged_onInputDeviceChanged() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify({
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(ByteArray::class.java),
- ArgumentMatchers.anyInt(),
- )
- }, Mockito.times(0))
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify({
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(ByteArray::class.java),
+ ArgumentMatchers.anyInt(),
+ )
+ }, Mockito.times(0))
}
@Test
@@ -975,18 +659,16 @@ class KeyboardLayoutManagerTests {
Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.times(1)
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.times(1)
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
@Test
@@ -996,18 +678,16 @@ class KeyboardLayoutManagerTests {
Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.never()
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.never()
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
private fun assertCorrectLayout(
@@ -1019,7 +699,7 @@ class KeyboardLayoutManagerTests {
device.identifier, USER_ID, imeInfo, imeSubtype
)
assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+ "getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
expectedLayout,
result.layoutDescriptor
)
@@ -1123,21 +803,4 @@ class KeyboardLayoutManagerTests {
info.serviceInfo.name = RECEIVER_NAME
return info
}
-
- private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
- init {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui", enabled.toString()
- )
- }
-
- override fun close() {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui",
- ""
- )
- }
- }
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index d9a4c261ee15..5cdfb2858b33 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -90,7 +90,7 @@ public class LegacyProtoLogImplTest {
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, new TreeMap<>());
+ 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
}
@After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
index a96389046d6a..001a09a0225a 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
@@ -67,7 +67,7 @@ public class PerfettoDataSourceTest {
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource(() -> {}, () -> {}, () -> {});
+ final ProtoLogDataSource ds = new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
@@ -154,7 +154,7 @@ public class PerfettoDataSourceTest {
private ProtoLogDataSource.TlsState createTlsState(
DataSourceConfigOuterClass.DataSourceConfig config) {
final ProtoLogDataSource ds =
- Mockito.spy(new ProtoLogDataSource(() -> {}, () -> {}, () -> {}));
+ Mockito.spy(new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {}));
ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 548adeff07b2..f6ac080ebf73 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -65,6 +65,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -95,6 +96,7 @@ public class PerfettoProtoLogImplTest {
private PerfettoProtoLogImpl mProtoLog;
private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder;
private File mFile;
+ private Runnable mCacheUpdater;
private ProtoLogViewerConfigReader mReader;
@@ -152,9 +154,11 @@ public class PerfettoProtoLogImplTest {
Mockito.when(viewerConfigInputStreamProvider.getInputStream())
.thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
+ mCacheUpdater = () -> {};
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog =
- new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
+ mProtoLog = new PerfettoProtoLogImpl(
+ viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
+ () -> mCacheUpdater.run());
}
@After
@@ -500,7 +504,8 @@ public class PerfettoProtoLogImplTest {
PerfettoTraceMonitor traceMonitor =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
- TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG, true)))
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ true)))
.build();
try {
traceMonitor.start();
@@ -526,6 +531,142 @@ public class PerfettoProtoLogImplTest {
Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
}
+ @Test
+ public void cacheIsUpdatedWhenTracesStartAndStop() {
+ final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0);
+ mCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+
+ PerfettoTraceMonitor traceMonitor1 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)))
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)))
+ .build();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(0);
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(1);
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(2);
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(3);
+
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(4);
+ }
+
+ @Test
+ public void isEnabledUpdatesBasedOnRunningTraces() {
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
+
+ PerfettoTraceMonitor traceMonitor1 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)))
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)))
+ .build();
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
+ LogLevel.VERBOSE)).isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index 081da11f2aa8..489ef4444e1d 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -66,6 +66,7 @@ import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -220,43 +221,36 @@ public class CrashRecoveryTest {
RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
- int bootCounter = 0;
+
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
- bootCounter += 1;
}
+
verify(rescuePartyObserver).executeBootLoopMitigation(1);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- bootCounter += 1;
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(2);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
- int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
- for (int i = 0; i < bootLoopThreshold; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(3);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(4);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(5);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(6);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
}
@@ -268,11 +262,11 @@ public class CrashRecoveryTest {
setUpRollbackPackageHealthObserver(watchdog);
verify(rollbackObserver, never()).executeBootLoopMitigation(1);
- int bootCounter = 0;
+
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
- bootCounter += 1;
}
+
verify(rollbackObserver).executeBootLoopMitigation(1);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
@@ -280,19 +274,16 @@ public class CrashRecoveryTest {
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
ROLLBACK_INFO_MANUAL));
- int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
- for (int i = 0; i < bootLoopThreshold; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rollbackObserver).executeBootLoopMitigation(2);
verify(rollbackObserver, never()).executeBootLoopMitigation(3);
// Update the list of available rollbacks after executing bootloop mitigation once
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rollbackObserver, never()).executeBootLoopMitigation(3);
}
@@ -305,27 +296,21 @@ public class CrashRecoveryTest {
verify(rescuePartyObserver, never()).executeBootLoopMitigation(1);
verify(rollbackObserver, never()).executeBootLoopMitigation(1);
- int bootCounter = 0;
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
- bootCounter += 1;
}
verify(rescuePartyObserver).executeBootLoopMitigation(1);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
verify(rollbackObserver, never()).executeBootLoopMitigation(1);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- bootCounter += 1;
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(2);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- bootCounter += 1;
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver, never()).executeBootLoopMitigation(3);
verify(rollbackObserver).executeBootLoopMitigation(1);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
@@ -333,43 +318,46 @@ public class CrashRecoveryTest {
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH,
ROLLBACK_INFO_MANUAL));
- int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter;
- for (int i = 0; i < bootLoopThreshold; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(3);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(4);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(4);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(5);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(5);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
verify(rollbackObserver, never()).executeBootLoopMitigation(2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver, never()).executeBootLoopMitigation(6);
verify(rollbackObserver).executeBootLoopMitigation(2);
verify(rollbackObserver, never()).executeBootLoopMitigation(3);
// Update the list of available rollbacks after executing bootloop mitigation
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL));
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) {
- watchdog.noteBoot();
- }
+ watchdog.noteBoot();
+
verify(rescuePartyObserver).executeBootLoopMitigation(6);
verify(rescuePartyObserver, never()).executeBootLoopMitigation(7);
verify(rollbackObserver, never()).executeBootLoopMitigation(3);
+
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
+ Mockito.reset(rescuePartyObserver);
+
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ }
+ verify(rescuePartyObserver).executeBootLoopMitigation(1);
+ verify(rescuePartyObserver, never()).executeBootLoopMitigation(2);
}
RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) {
@@ -506,16 +494,9 @@ public class CrashRecoveryTest {
}
try {
- if (Flags.recoverabilityDetection()) {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
- PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
- } else {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
- }
doAnswer((Answer<Integer>) invocationOnMock -> {
String storedValue = mCrashRecoveryPropertiesMap
@@ -640,5 +621,16 @@ public class CrashRecoveryTest {
public long uptimeMillis() {
return mUpTimeMillis;
}
+ public void moveTimeForward(long milliSeconds) {
+ mUpTimeMillis += milliSeconds;
+ }
+ }
+
+ private void moveTimeForwardAndDispatch(long milliSeconds) {
+ // Exhaust all due runnables now which shouldn't be executed after time-leap
+ mTestLooper.dispatchAll();
+ mTestClock.moveTimeForward(milliSeconds);
+ mTestLooper.moveTimeForward(milliSeconds);
+ mTestLooper.dispatchAll();
}
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 4f27e06083ba..093923f3ed53 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@ import android.os.test.TestLooper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Xml;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
@@ -1224,7 +1224,7 @@ public class PackageWatchdogTest {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
watchdog.registerHealthObserver(bootObserver);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) {
+ for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
@@ -1262,22 +1262,6 @@ public class PackageWatchdogTest {
}
/**
- * Ensure that boot loop mitigation is not done when the number of boots does not meet the
- * threshold.
- */
- @Test
- public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityHighImpact() {
- PackageWatchdog watchdog = createWatchdog();
- TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
- watchdog.registerHealthObserver(bootObserver);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; i++) {
- watchdog.noteBoot();
- }
- assertThat(bootObserver.mitigatedBootLoop()).isFalse();
- }
-
- /**
* Ensure that boot loop mitigation is done for the observer with the lowest user impact
*/
@Test
@@ -1306,7 +1290,7 @@ public class PackageWatchdogTest {
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
watchdog.registerHealthObserver(bootObserver1);
watchdog.registerHealthObserver(bootObserver2);
- for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) {
+ for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
@@ -1349,9 +1333,7 @@ public class PackageWatchdogTest {
watchdog.noteBoot();
}
for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
watchdog.noteBoot();
- }
}
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
@@ -1360,38 +1342,7 @@ public class PackageWatchdogTest {
watchdog.noteBoot();
}
for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
watchdog.noteBoot();
- }
- }
-
- assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
- }
-
- @Test
- public void testMultipleBootLoopMitigationRecoverabilityHighImpact() {
- PackageWatchdog watchdog = createWatchdog();
- TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_80);
- watchdog.registerHealthObserver(bootObserver);
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) {
- watchdog.noteBoot();
- }
- for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
- watchdog.noteBoot();
- }
- }
-
- moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1);
-
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) {
- watchdog.noteBoot();
- }
- for (int i = 0; i < 4; i++) {
- for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) {
- watchdog.noteBoot();
- }
}
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
@@ -1642,8 +1593,7 @@ public class PackageWatchdogTest {
mSpyBootThreshold = spy(watchdog.new BootThreshold(
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
- PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
+ PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
watchdog.saveAllObserversBootMitigationCountToMetadata(filePath);
@@ -1798,16 +1748,9 @@ public class PackageWatchdogTest {
mCrashRecoveryPropertiesMap = new HashMap<>();
try {
- if (Flags.recoverabilityDetection()) {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS,
- PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT));
- } else {
- mSpyBootThreshold = spy(watchdog.new BootThreshold(
+ mSpyBootThreshold = spy(watchdog.new BootThreshold(
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS));
- }
doAnswer((Answer<Integer>) invocationOnMock -> {
String storedValue = mCrashRecoveryPropertiesMap
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
index a62103e0030b..755833234e02 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -16,10 +16,16 @@
package com.android.internal.telephony.tests;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -72,6 +78,22 @@ public class TelephonyUtilsTest {
// getSubscriptionUserHandle should be called if subID is active.
verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
}
+
+ @Test
+ public void testIsValidPlmn() {
+ assertTrue(TelephonyUtils.isValidPlmn("310260"));
+ assertTrue(TelephonyUtils.isValidPlmn("45006"));
+ assertFalse(TelephonyUtils.isValidPlmn("1234567"));
+ assertFalse(TelephonyUtils.isValidPlmn("1234"));
+ }
+
+ @Test
+ public void testIsValidService() {
+ assertTrue(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE));
+ assertTrue(TelephonyUtils.isValidService(LAST_SERVICE_TYPE));
+ assertFalse(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE - 1));
+ assertFalse(TelephonyUtils.isValidService(LAST_SERVICE_TYPE + 1));
+ }
}
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index a16a7eafc8e8..f0bea3f3c28a 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -21,6 +21,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_usb",
}
android_test {
diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp
index 92c271165ad7..c012cce494e2 100644
--- a/tests/UsbTests/Android.bp
+++ b/tests/UsbTests/Android.bp
@@ -21,6 +21,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_usb",
}
android_test {
@@ -36,6 +37,8 @@ android_test {
"services.usb",
"truth",
"UsbManagerTestLib",
+ "android.hardware.usb.flags-aconfig-java",
+ "flag-junit",
],
jni_libs: [
// Required for ExtendedMockito
diff --git a/tests/UsbTests/TEST_MAPPING b/tests/UsbTests/TEST_MAPPING
new file mode 100644
index 000000000000..70134b818722
--- /dev/null
+++ b/tests/UsbTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "UsbTests"
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
new file mode 100644
index 000000000000..b506d74d6500
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.usb;
+
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.flags.Flags;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.usb.UsbService}
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbServiceTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UsbPortManager mUsbPortManager;
+ @Mock
+ private UsbAlsaManager mUsbAlsaManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UsbSettingsManager mUsbSettingsManager;
+ @Mock
+ private IUsbOperationInternal mIUsbOperationInternal;
+
+ private static final String TEST_PORT_ID = "123";
+
+ private static final int TEST_TRANSACTION_ID = 1;
+
+ private static final int TEST_FIRST_CALLER_ID = 1000;
+
+ private static final int TEST_SECOND_CALLER_ID = 2000;
+
+ private UsbService mUsbService;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager,
+ mUsbSettingsManager);
+ }
+
+ /**
+ * Verify enableUsbData successfully disables USB port without error
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPort_SuccessfullyDisabled() {
+ boolean enableState = false;
+ when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null)).thenReturn(true);
+
+ assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
+
+ verify(mUsbPortManager, times(1)).enableUsbData(TEST_PORT_ID,
+ enableState, TEST_TRANSACTION_ID, mIUsbOperationInternal, null);
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+
+ /**
+ * Verify enableUsbData successfully enables USB port without error given no other stakers
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPortWhenNoOtherStakers_SuccessfullyEnabledUsb() {
+ boolean enableState = true;
+ when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null))
+ .thenReturn(true);
+
+ assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other stakers are present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPortWithOtherStakers_DoesNotToEnableUsb() throws RemoteException {
+ mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+ clearInvocations(mUsbPortManager);
+
+ assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, true,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_SECOND_CALLER_ID));
+
+ verifyZeroInteractions(mUsbPortManager);
+ verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+
+ /**
+ * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void enableUsbWhileDockedWhenThereAreOtherStakers_DoesNotEnableUsb()
+ throws RemoteException {
+ mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+
+ mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, 0,
+ mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+
+ verify(mUsbPortManager, never()).enableUsbDataWhileDocked(any(),
+ anyLong(), any(), any());
+ verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+
+ /**
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
+ * not present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnableUsb()
+ throws RemoteException {
+ mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+
+ verify(mUsbPortManager, times(1))
+ .enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null);
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+}
diff --git a/tools/app_metadata_bundles/Android.bp b/tools/app_metadata_bundles/Android.bp
index a012dca19904..dced50d7ee3b 100644
--- a/tools/app_metadata_bundles/Android.bp
+++ b/tools/app_metadata_bundles/Android.bp
@@ -13,6 +13,9 @@ java_library_host {
srcs: [
"src/lib/java/**/*.java",
],
+ static_libs: [
+ "guava",
+ ],
}
java_binary_host {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index 9dd55314e844..c1c520e99cac 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -16,7 +16,10 @@
package com.android.asllib;
+import com.android.asllib.marshallable.AndroidSafetyLabel;
+import com.android.asllib.marshallable.AndroidSafetyLabelFactory;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
deleted file mode 100644
index a0a75377e988..000000000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.asllib;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
- * {@link DataCategory}, and {@link DataType}
- */
-public class DataTypeConstants {
- /** Data types for {@link DataCategoryConstants.CATEGORY_PERSONAL} */
- public static final String TYPE_NAME = "name";
-
- public static final String TYPE_EMAIL_ADDRESS = "email_address";
- public static final String TYPE_PHONE_NUMBER = "phone_number";
- public static final String TYPE_RACE_ETHNICITY = "race_ethnicity";
- public static final String TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS =
- "political_or_religious_beliefs";
- public static final String TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
- "sexual_orientation_or_gender_identity";
- public static final String TYPE_PERSONAL_IDENTIFIERS = "personal_identifiers";
- public static final String TYPE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_FINANCIAL} */
- public static final String TYPE_CARD_BANK_ACCOUNT = "card_bank_account";
-
- public static final String TYPE_PURCHASE_HISTORY = "purchase_history";
- public static final String TYPE_CREDIT_SCORE = "credit_score";
- public static final String TYPE_FINANCIAL_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_LOCATION} */
- public static final String TYPE_APPROX_LOCATION = "approx_location";
-
- public static final String TYPE_PRECISE_LOCATION = "precise_location";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE} */
- public static final String TYPE_EMAILS = "emails";
-
- public static final String TYPE_TEXT_MESSAGES = "text_messages";
- public static final String TYPE_EMAIL_TEXT_MESSAGE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_PHOTO_VIDEO} */
- public static final String TYPE_PHOTOS = "photos";
-
- public static final String TYPE_VIDEOS = "videos";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_AUDIO} */
- public static final String TYPE_SOUND_RECORDINGS = "sound_recordings";
-
- public static final String TYPE_MUSIC_FILES = "music_files";
- public static final String TYPE_AUDIO_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_STORAGE} */
- public static final String TYPE_FILES_DOCS = "files_docs";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_HEALTH_FITNESS} */
- public static final String TYPE_HEALTH = "health";
-
- public static final String TYPE_FITNESS = "fitness";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_CONTACTS} */
- public static final String TYPE_CONTACTS = "contacts";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_CALENDAR} */
- public static final String TYPE_CALENDAR = "calendar";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_IDENTIFIERS} */
- public static final String TYPE_IDENTIFIERS_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_APP_PERFORMANCE} */
- public static final String TYPE_CRASH_LOGS = "crash_logs";
-
- public static final String TYPE_PERFORMANCE_DIAGNOSTICS = "performance_diagnostics";
- public static final String TYPE_APP_PERFORMANCE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_ACTIONS_IN_APP} */
- public static final String TYPE_USER_INTERACTION = "user_interaction";
-
- public static final String TYPE_IN_APP_SEARCH_HISTORY = "in_app_search_history";
- public static final String TYPE_INSTALLED_APPS = "installed_apps";
- public static final String TYPE_USER_GENERATED_CONTENT = "user_generated_content";
- public static final String TYPE_ACTIONS_IN_APP_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING} */
- public static final String TYPE_WEB_BROWSING_HISTORY = "web_browsing_history";
-
- /** Set of valid categories */
- public static final Set<String> VALID_TYPES =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- TYPE_NAME,
- TYPE_EMAIL_ADDRESS,
- TYPE_PHONE_NUMBER,
- TYPE_RACE_ETHNICITY,
- TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS,
- TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
- TYPE_PERSONAL_IDENTIFIERS,
- TYPE_OTHER,
- TYPE_CARD_BANK_ACCOUNT,
- TYPE_PURCHASE_HISTORY,
- TYPE_CREDIT_SCORE,
- TYPE_FINANCIAL_OTHER,
- TYPE_APPROX_LOCATION,
- TYPE_PRECISE_LOCATION,
- TYPE_EMAILS,
- TYPE_TEXT_MESSAGES,
- TYPE_EMAIL_TEXT_MESSAGE_OTHER,
- TYPE_PHOTOS,
- TYPE_VIDEOS,
- TYPE_SOUND_RECORDINGS,
- TYPE_MUSIC_FILES,
- TYPE_AUDIO_OTHER,
- TYPE_FILES_DOCS,
- TYPE_HEALTH,
- TYPE_FITNESS,
- TYPE_CONTACTS,
- TYPE_CALENDAR,
- TYPE_IDENTIFIERS_OTHER,
- TYPE_CRASH_LOGS,
- TYPE_PERFORMANCE_DIAGNOSTICS,
- TYPE_APP_PERFORMANCE_OTHER,
- TYPE_USER_INTERACTION,
- TYPE_IN_APP_SEARCH_HISTORY,
- TYPE_INSTALLED_APPS,
- TYPE_USER_GENERATED_CONTENT,
- TYPE_ACTIONS_IN_APP_OTHER,
- TYPE_WEB_BROWSING_HISTORY)));
-
- /** Returns {@link Set} of valid {@link String} category keys */
- public static Set<String> getValidDataTypes() {
- return VALID_TYPES;
- }
-
- private DataTypeConstants() {
- /* do nothing - hide constructor */
- }
-}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index cdb559b52c0e..112b92c9aebb 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index 3dc725b5452b..b69c30f7f522 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index f94b6591cd10..3f1ddebefe99 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 26d94c16c7f0..59a437d7ece5 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.Arrays;
import java.util.List;
public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
@@ -37,31 +37,22 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE);
String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION);
- Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS);
- Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS);
+ Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, true);
+ Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, true);
Boolean adsFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING);
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, true);
Boolean securityFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING);
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, true);
String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY);
List<String> securityEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_SECURITY_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, true);
List<String> firstPartyEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS, true);
List<String> serviceProviderEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, true);
String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY);
String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL);
String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 4e64ab0c53c1..48747ccbcff6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index b8f9f0ef6235..a49b3e77155b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.MalformedXmlException;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index b9e06fbdfc7e..4d67162b442d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.DataCategoryConstants;
+import com.android.asllib.util.DataTypeConstants;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index d2b671271561..37d99e7ef85e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+import com.android.asllib.util.DataTypeConstants;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
@@ -30,11 +32,17 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory>
String categoryName = null;
Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
for (Element ele : elements) {
- categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
- String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
- if (!DataTypeConstants.getValidDataTypes().contains(dataTypeName)) {
+ categoryName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_CATEGORY, true);
+ String dataTypeName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_TYPE, true);
+ if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) {
throw new MalformedXmlException(
- String.format("Unrecognized data type name: %s", dataTypeName));
+ String.format("Unrecognized data category %s", categoryName));
+ }
+ if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unrecognized data type name %s for category %s",
+ dataTypeName, categoryName));
}
dataTypeMap.put(
dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele)));
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 96ec93c28c87..7516faf9f77a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.DataCategoryConstants;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -41,24 +44,23 @@ public class DataLabels implements AslMarshallable {
}
/**
- * Returns the data accessed {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data accessed {@link Map} of {@link DataCategoryConstants} to {@link
+ * DataCategory}
*/
public Map<String, DataCategory> getDataAccessed() {
return mDataAccessed;
}
/**
- * Returns the data collected {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data collected {@link Map} of {@link DataCategoryConstants} to {@link
+ * DataCategory}
*/
public Map<String, DataCategory> getDataCollected() {
return mDataCollected;
}
/**
- * Returns the data shared {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data shared {@link Map} of {@link DataCategoryConstants} to {@link DataCategory}
*/
public Map<String, DataCategory> getDataShared() {
return mDataShared;
@@ -70,7 +72,7 @@ public class DataLabels implements AslMarshallable {
Element dataLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
- maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_ACCESSED);
+ maybeAppendDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.OD_NAME_DATA_ACCESSED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index 79edab7631f0..dc77fd08aa53 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.DataCategoryConstants;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index d011cfeef363..347136237966 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index 27c1b599fec7..ed434cda0823 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -14,30 +14,40 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.stream.Collectors;
public class DataTypeFactory implements AslMarshallableFactory<DataType> {
/** Creates a {@link DataType} from the human-readable DOM element. */
@Override
- public DataType createFromHrElements(List<Element> elements) {
+ public DataType createFromHrElements(List<Element> elements) throws MalformedXmlException {
Element hrDataTypeEle = XmlUtils.getSingleElement(elements);
String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
List<DataType.Purpose> purposes =
- Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|"))
+ XmlUtils.getPipelineSplitAttr(hrDataTypeEle, XmlUtils.HR_ATTR_PURPOSES, true)
+ .stream()
.map(DataType.Purpose::forString)
.collect(Collectors.toList());
+ if (purposes.isEmpty()) {
+ throw new MalformedXmlException(String.format("Found no purpose in: %s", dataTypeName));
+ }
+ if (new HashSet<>(purposes).size() != purposes.size()) {
+ throw new MalformedXmlException(
+ String.format("Found non-unique purposes in: %s", dataTypeName));
+ }
Boolean isCollectionOptional =
- XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL);
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, false);
Boolean isSharingOptional =
- XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL);
- Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL);
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, false);
+ Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, false);
return new DataType(
dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
index 44a5b129e428..382a1f0d0eca 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
index 4961892b10c3..b5310bac232a 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 40ef48dc5334..22c3fd8f2a1c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index ab81b1d56033..6bf8ef3df32d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
@@ -40,7 +41,9 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels>
.createFromHrElements(
XmlUtils.listOf(
XmlUtils.getSingleChildElement(
- safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS)));
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_DATA_LABELS,
+ false)));
return new SafetyLabels(version, dataLabels);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 93d9c2b080c5..595d748b59af 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index c8c1c7beba24..f99955993d6c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index 88717b9568b8..ddd3557616ca 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 13a7eb62fedd..d9c2af41fcac 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java
index b364c8b37194..b5ae54c5bc00 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.util;
+import com.android.asllib.marshallable.DataCategory;
+import com.android.asllib.marshallable.DataType;
+import com.android.asllib.marshallable.SafetyLabels;
import java.util.Arrays;
import java.util.Collections;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java
new file mode 100644
index 000000000000..358d575c1d56
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.util;
+
+import com.android.asllib.marshallable.DataCategory;
+import com.android.asllib.marshallable.DataType;
+import com.android.asllib.marshallable.SafetyLabels;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
+ * {@link DataCategory}, and {@link DataType}
+ */
+public class DataTypeConstants {
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PERSONAL} */
+ public static final String TYPE_NAME = "name";
+
+ public static final String TYPE_EMAIL_ADDRESS = "email_address";
+ public static final String TYPE_PHYSICAL_ADDRESS = "physical_address";
+ public static final String TYPE_PHONE_NUMBER = "phone_number";
+ public static final String TYPE_RACE_ETHNICITY = "race_ethnicity";
+ public static final String TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS =
+ "political_or_religious_beliefs";
+ public static final String TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
+ "sexual_orientation_or_gender_identity";
+ public static final String TYPE_PERSONAL_IDENTIFIERS = "personal_identifiers";
+ public static final String TYPE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_FINANCIAL} */
+ public static final String TYPE_CARD_BANK_ACCOUNT = "card_bank_account";
+
+ public static final String TYPE_PURCHASE_HISTORY = "purchase_history";
+ public static final String TYPE_CREDIT_SCORE = "credit_score";
+ public static final String TYPE_FINANCIAL_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_LOCATION} */
+ public static final String TYPE_APPROX_LOCATION = "approx_location";
+
+ public static final String TYPE_PRECISE_LOCATION = "precise_location";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE} */
+ public static final String TYPE_EMAILS = "emails";
+
+ public static final String TYPE_TEXT_MESSAGES = "text_messages";
+ public static final String TYPE_EMAIL_TEXT_MESSAGE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PHOTO_VIDEO} */
+ public static final String TYPE_PHOTOS = "photos";
+
+ public static final String TYPE_VIDEOS = "videos";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_AUDIO} */
+ public static final String TYPE_SOUND_RECORDINGS = "sound_recordings";
+
+ public static final String TYPE_MUSIC_FILES = "music_files";
+ public static final String TYPE_AUDIO_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_STORAGE} */
+ public static final String TYPE_FILES_DOCS = "files_docs";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_HEALTH_FITNESS} */
+ public static final String TYPE_HEALTH = "health";
+
+ public static final String TYPE_FITNESS = "fitness";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CONTACTS} */
+ public static final String TYPE_CONTACTS = "contacts";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CALENDAR} */
+ public static final String TYPE_CALENDAR = "calendar";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_IDENTIFIERS} */
+ public static final String TYPE_IDENTIFIERS_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_APP_PERFORMANCE} */
+ public static final String TYPE_CRASH_LOGS = "crash_logs";
+
+ public static final String TYPE_PERFORMANCE_DIAGNOSTICS = "performance_diagnostics";
+ public static final String TYPE_APP_PERFORMANCE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_ACTIONS_IN_APP} */
+ public static final String TYPE_USER_INTERACTION = "user_interaction";
+
+ public static final String TYPE_IN_APP_SEARCH_HISTORY = "in_app_search_history";
+ public static final String TYPE_INSTALLED_APPS = "installed_apps";
+ public static final String TYPE_USER_GENERATED_CONTENT = "user_generated_content";
+ public static final String TYPE_ACTIONS_IN_APP_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING} */
+ public static final String TYPE_WEB_BROWSING_HISTORY = "web_browsing_history";
+
+ /** Set of valid categories */
+ private static final Map<String, Set<String>> VALID_DATA_TYPES =
+ new ImmutableMap.Builder<String, Set<String>>()
+ .put(
+ DataCategoryConstants.CATEGORY_PERSONAL,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_NAME,
+ DataTypeConstants.TYPE_EMAIL_ADDRESS,
+ DataTypeConstants.TYPE_PHYSICAL_ADDRESS,
+ DataTypeConstants.TYPE_PHONE_NUMBER,
+ DataTypeConstants.TYPE_RACE_ETHNICITY,
+ DataTypeConstants.TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS,
+ DataTypeConstants.TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
+ DataTypeConstants.TYPE_PERSONAL_IDENTIFIERS,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_FINANCIAL,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_CARD_BANK_ACCOUNT,
+ DataTypeConstants.TYPE_PURCHASE_HISTORY,
+ DataTypeConstants.TYPE_CREDIT_SCORE,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_LOCATION,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_APPROX_LOCATION,
+ DataTypeConstants.TYPE_PRECISE_LOCATION))
+ .put(
+ DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_EMAILS,
+ DataTypeConstants.TYPE_TEXT_MESSAGES,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_PHOTO_VIDEO,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_PHOTOS, DataTypeConstants.TYPE_VIDEOS))
+ .put(
+ DataCategoryConstants.CATEGORY_AUDIO,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_SOUND_RECORDINGS,
+ DataTypeConstants.TYPE_MUSIC_FILES,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_STORAGE,
+ ImmutableSet.of(DataTypeConstants.TYPE_FILES_DOCS))
+ .put(
+ DataCategoryConstants.CATEGORY_HEALTH_FITNESS,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_HEALTH, DataTypeConstants.TYPE_FITNESS))
+ .put(
+ DataCategoryConstants.CATEGORY_CONTACTS,
+ ImmutableSet.of(DataTypeConstants.TYPE_CONTACTS))
+ .put(
+ DataCategoryConstants.CATEGORY_CALENDAR,
+ ImmutableSet.of(DataTypeConstants.TYPE_CALENDAR))
+ .put(
+ DataCategoryConstants.CATEGORY_IDENTIFIERS,
+ ImmutableSet.of(DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_APP_PERFORMANCE,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_CRASH_LOGS,
+ DataTypeConstants.TYPE_PERFORMANCE_DIAGNOSTICS,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_ACTIONS_IN_APP,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_USER_INTERACTION,
+ DataTypeConstants.TYPE_IN_APP_SEARCH_HISTORY,
+ DataTypeConstants.TYPE_INSTALLED_APPS,
+ DataTypeConstants.TYPE_USER_GENERATED_CONTENT,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING,
+ ImmutableSet.of(DataTypeConstants.TYPE_WEB_BROWSING_HISTORY))
+ .buildOrThrow();
+
+ /** Returns {@link Set} of valid {@link String} category keys */
+ public static Map<String, Set<String>> getValidDataTypes() {
+ return VALID_DATA_TYPES;
+ }
+
+ private DataTypeConstants() {
+ /* do nothing - hide constructor */
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index cc8fe79cb579..691f92fca54b 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
-
-import com.android.asllib.util.MalformedXmlException;
+package com.android.asllib.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -259,21 +257,42 @@ public class XmlUtils {
}
/** Tries getting required version attribute and throws exception if it doesn't exist */
- public static Long tryGetVersion(Element ele) {
+ public static Long tryGetVersion(Element ele) throws MalformedXmlException {
long version;
try {
version = Long.parseLong(ele.getAttribute(XmlUtils.HR_ATTR_VERSION));
} catch (Exception e) {
- throw new IllegalArgumentException(
+ throw new MalformedXmlException(
String.format(
"Malformed or missing required version in: %s", ele.getTagName()));
}
return version;
}
- /** Gets an optional Boolean attribute. */
- public static Boolean getBoolAttr(Element ele, String attrName) {
- return XmlUtils.fromString(ele.getAttribute(attrName));
+ /** Gets a pipeline-split attribute. */
+ public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required)
+ throws MalformedXmlException {
+ List<String> list = Arrays.stream(ele.getAttribute(attrName).split("\\|")).toList();
+ if ((list.isEmpty() || list.get(0).isEmpty()) && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Delimited string %s was required but missing, in %s.",
+ attrName, ele.getTagName()));
+ }
+ return list;
+ }
+
+ /** Gets a Boolean attribute. */
+ public static Boolean getBoolAttr(Element ele, String attrName, boolean required)
+ throws MalformedXmlException {
+ Boolean b = XmlUtils.fromString(ele.getAttribute(attrName));
+ if (b == null && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Boolean %s was required but missing, in %s.",
+ attrName, ele.getTagName()));
+ }
+ return b;
}
/** Gets a required String attribute. */
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
new file mode 100644
index 000000000000..03e8ac6d11c0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib;
+
+import com.android.asllib.marshallable.AndroidSafetyLabelTest;
+import com.android.asllib.marshallable.DataCategoryTest;
+import com.android.asllib.marshallable.DataLabelsTest;
+import com.android.asllib.marshallable.DeveloperInfoTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ AslgenTests.class,
+ AndroidSafetyLabelTest.class,
+ DeveloperInfoTest.class,
+ DataCategoryTest.class,
+ DataLabelsTest.class,
+})
+public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
index 3026f8bec2ed..5f43008d3dc6 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -14,40 +14,26 @@
* limitations under the License.
*/
-package com.android.aslgen;
+package com.android.asllib;
import static org.junit.Assert.assertEquals;
-import com.android.asllib.AndroidSafetyLabel;
-import com.android.asllib.AslConverter;
+import com.android.asllib.marshallable.AndroidSafetyLabel;
+import com.android.asllib.testutils.TestUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
@RunWith(JUnit4.class)
public class AslgenTests {
- private static final String VALID_MAPPINGS_PATH = "com/android/aslgen/validmappings";
+ private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings";
private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
private static final String HR_XML_FILENAME = "hr.xml";
private static final String OD_XML_FILENAME = "od.xml";
@@ -78,28 +64,9 @@ public class AslgenTests {
String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
System.out.println("out: " + out);
- assertEquals(getFormattedXml(out), getFormattedXml(odContents));
+ assertEquals(
+ TestUtils.getFormattedXml(out, false),
+ TestUtils.getFormattedXml(odContents, false));
}
}
-
- private static String getFormattedXml(String xmlStr)
- throws ParserConfigurationException, IOException, SAXException, TransformerException {
- InputStream stream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- Document document = factory.newDocumentBuilder().parse(stream);
-
- TransformerFactory transformerFactory = TransformerFactory.newInstance();
- Transformer transformer = transformerFactory.newTransformer();
- transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
- transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
-
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- StreamResult streamResult = new StreamResult(outStream); // out
- DOMSource domSource = new DOMSource(document);
- transformer.transform(domSource, streamResult);
-
- return outStream.toString(StandardCharsets.UTF_8);
- }
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
new file mode 100644
index 000000000000..013700728e50
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class AndroidSafetyLabelTest {
+ private static final String ANDROID_SAFETY_LABEL_HR_PATH =
+ "com/android/asllib/androidsafetylabel/hr";
+ private static final String ANDROID_SAFETY_LABEL_OD_PATH =
+ "com/android/asllib/androidsafetylabel/od";
+
+ private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_SAFETY_LABELS_FILE_NAME = "with-safety-labels.xml";
+ private static final String WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME =
+ "with-system-app-safety-label.xml";
+ private static final String WITH_TRANSPARENCY_INFO_FILE_NAME = "with-transparency-info.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for android safety label missing version. */
+ @Test
+ public void testAndroidSafetyLabelMissingVersion() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelMissingVersion.");
+ hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ }
+
+ /** Test for android safety label valid empty. */
+ @Test
+ public void testAndroidSafetyLabelValidEmptyFile() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelValidEmptyFile.");
+ testHrToOdAndroidSafetyLabel(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for android safety label with safety labels. */
+ @Test
+ public void testAndroidSafetyLabelWithSafetyLabels() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithSafetyLabels.");
+ testHrToOdAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
+ }
+
+ /** Test for android safety label with system app safety label. */
+ @Test
+ public void testAndroidSafetyLabelWithSystemAppSafetyLabel() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithSystemAppSafetyLabel.");
+ testHrToOdAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
+ }
+
+ /** Test for android safety label with transparency info. */
+ @Test
+ public void testAndroidSafetyLabelWithTransparencyInfo() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithTransparencyInfo.");
+ testHrToOdAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(
+ new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_HR_PATH, fileName);
+ }
+
+ private void testHrToOdAndroidSafetyLabel(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new AndroidSafetyLabelFactory(),
+ ANDROID_SAFETY_LABEL_HR_PATH,
+ ANDROID_SAFETY_LABEL_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
new file mode 100644
index 000000000000..a015e2eacac5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AppInfoTest {
+ private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr";
+ private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od";
+ public static final List<String> REQUIRED_FIELD_NAMES =
+ List.of(
+ "title",
+ "description",
+ "containsAds",
+ "obeyAps",
+ "adsFingerprinting",
+ "securityFingerprinting",
+ "privacyPolicy",
+ "securityEndpoints",
+ "firstPartyEndpoints",
+ "serviceProviderEndpoints",
+ "category",
+ "email");
+ public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website");
+
+ private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for all fields valid. */
+ @Test
+ public void testAllFieldsValid() throws Exception {
+ System.out.println("starting testAllFieldsValid.");
+ testHrToOdAppInfo(ALL_FIELDS_VALID_FILE_NAME);
+ }
+
+ /** Tests missing required fields fails. */
+ @Test
+ public void testMissingRequiredFields() throws Exception {
+ System.out.println("Starting testMissingRequiredFields");
+ for (String reqField : REQUIRED_FIELD_NAMES) {
+ System.out.println("testing missing required field: " + reqField);
+ var appInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ appInfoEle.get(0).removeAttribute(reqField);
+
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+ }
+ }
+
+ /** Tests missing optional fields passes. */
+ @Test
+ public void testMissingOptionalFields() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES) {
+ var ele =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ ele.get(0).removeAttribute(optField);
+ AppInfo appInfo = new AppInfoFactory().createFromHrElements(ele);
+ appInfo.toOdDomElements(mDoc);
+ }
+ }
+
+ private void testHrToOdAppInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc, new AppInfoFactory(), APP_INFO_HR_PATH, APP_INFO_OD_PATH, fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
new file mode 100644
index 000000000000..822f1753f662
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class DataCategoryTest {
+ private static final String DATA_CATEGORY_HR_PATH = "com/android/asllib/datacategory/hr";
+ private static final String DATA_CATEGORY_OD_PATH = "com/android/asllib/datacategory/od";
+
+ private static final String VALID_PERSONAL_FILE_NAME = "data-category-personal.xml";
+ private static final String VALID_PARTIAL_PERSONAL_FILE_NAME =
+ "data-category-personal-partial.xml";
+ private static final String VALID_FINANCIAL_FILE_NAME = "data-category-financial.xml";
+ private static final String VALID_LOCATION_FILE_NAME = "data-category-location.xml";
+ private static final String VALID_EMAIL_TEXT_MESSAGE_FILE_NAME =
+ "data-category-email-text-message.xml";
+ private static final String VALID_PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml";
+ private static final String VALID_AUDIO_FILE_NAME = "data-category-audio.xml";
+ private static final String VALID_STORAGE_FILE_NAME = "data-category-storage.xml";
+ private static final String VALID_HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml";
+ private static final String VALID_CONTACTS_FILE_NAME = "data-category-contacts.xml";
+ private static final String VALID_CALENDAR_FILE_NAME = "data-category-calendar.xml";
+ private static final String VALID_IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml";
+ private static final String VALID_APP_PERFORMANCE_FILE_NAME =
+ "data-category-app-performance.xml";
+ private static final String VALID_ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml";
+ private static final String VALID_SEARCH_AND_BROWSING_FILE_NAME =
+ "data-category-search-and-browsing.xml";
+
+ private static final String EMPTY_PURPOSE_PERSONAL_FILE_NAME =
+ "data-category-personal-empty-purpose.xml";
+ private static final String MISSING_PURPOSE_PERSONAL_FILE_NAME =
+ "data-category-personal-missing-purpose.xml";
+ private static final String UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME =
+ "data-category-personal-unrecognized-type.xml";
+ private static final String UNRECOGNIZED_CATEGORY_FILE_NAME = "data-category-unrecognized.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for data category personal. */
+ @Test
+ public void testDataCategoryPersonal() throws Exception {
+ System.out.println("starting testDataCategoryPersonal.");
+ testHrToOdDataCategory(VALID_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for data category financial. */
+ @Test
+ public void testDataCategoryFinancial() throws Exception {
+ System.out.println("starting testDataCategoryFinancial.");
+ testHrToOdDataCategory(VALID_FINANCIAL_FILE_NAME);
+ }
+
+ /** Test for data category location. */
+ @Test
+ public void testDataCategoryLocation() throws Exception {
+ System.out.println("starting testDataCategoryLocation.");
+ testHrToOdDataCategory(VALID_LOCATION_FILE_NAME);
+ }
+
+ /** Test for data category email text message. */
+ @Test
+ public void testDataCategoryEmailTextMessage() throws Exception {
+ System.out.println("starting testDataCategoryEmailTextMessage.");
+ testHrToOdDataCategory(VALID_EMAIL_TEXT_MESSAGE_FILE_NAME);
+ }
+
+ /** Test for data category photo video. */
+ @Test
+ public void testDataCategoryPhotoVideo() throws Exception {
+ System.out.println("starting testDataCategoryPhotoVideo.");
+ testHrToOdDataCategory(VALID_PHOTO_VIDEO_FILE_NAME);
+ }
+
+ /** Test for data category audio. */
+ @Test
+ public void testDataCategoryAudio() throws Exception {
+ System.out.println("starting testDataCategoryAudio.");
+ testHrToOdDataCategory(VALID_AUDIO_FILE_NAME);
+ }
+
+ /** Test for data category storage. */
+ @Test
+ public void testDataCategoryStorage() throws Exception {
+ System.out.println("starting testDataCategoryStorage.");
+ testHrToOdDataCategory(VALID_STORAGE_FILE_NAME);
+ }
+
+ /** Test for data category health fitness. */
+ @Test
+ public void testDataCategoryHealthFitness() throws Exception {
+ System.out.println("starting testDataCategoryHealthFitness.");
+ testHrToOdDataCategory(VALID_HEALTH_FITNESS_FILE_NAME);
+ }
+
+ /** Test for data category contacts. */
+ @Test
+ public void testDataCategoryContacts() throws Exception {
+ System.out.println("starting testDataCategoryContacts.");
+ testHrToOdDataCategory(VALID_CONTACTS_FILE_NAME);
+ }
+
+ /** Test for data category calendar. */
+ @Test
+ public void testDataCategoryCalendar() throws Exception {
+ System.out.println("starting testDataCategoryCalendar.");
+ testHrToOdDataCategory(VALID_CALENDAR_FILE_NAME);
+ }
+
+ /** Test for data category identifiers. */
+ @Test
+ public void testDataCategoryIdentifiers() throws Exception {
+ System.out.println("starting testDataCategoryIdentifiers.");
+ testHrToOdDataCategory(VALID_IDENTIFIERS_FILE_NAME);
+ }
+
+ /** Test for data category app performance. */
+ @Test
+ public void testDataCategoryAppPerformance() throws Exception {
+ System.out.println("starting testDataCategoryAppPerformance.");
+ testHrToOdDataCategory(VALID_APP_PERFORMANCE_FILE_NAME);
+ }
+
+ /** Test for data category actions in app. */
+ @Test
+ public void testDataCategoryActionsInApp() throws Exception {
+ System.out.println("starting testDataCategoryActionsInApp.");
+ testHrToOdDataCategory(VALID_ACTIONS_IN_APP_FILE_NAME);
+ }
+
+ /** Test for data category search and browsing. */
+ @Test
+ public void testDataCategorySearchAndBrowsing() throws Exception {
+ System.out.println("starting testDataCategorySearchAndBrowsing.");
+ testHrToOdDataCategory(VALID_SEARCH_AND_BROWSING_FILE_NAME);
+ }
+
+ /** Test for data category search and browsing. */
+ @Test
+ public void testMissingOptionalsAllowed() throws Exception {
+ System.out.println("starting testMissingOptionalsAllowed.");
+ testHrToOdDataCategory(VALID_PARTIAL_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for empty purposes. */
+ @Test
+ public void testEmptyPurposesNotAllowed() throws Exception {
+ System.out.println("starting testEmptyPurposesNotAllowed.");
+ hrToOdExpectException(EMPTY_PURPOSE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for missing purposes. */
+ @Test
+ public void testMissingPurposesNotAllowed() throws Exception {
+ System.out.println("starting testMissingPurposesNotAllowed.");
+ hrToOdExpectException(MISSING_PURPOSE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for unrecognized type. */
+ @Test
+ public void testUnrecognizedTypeNotAllowed() throws Exception {
+ System.out.println("starting testUnrecognizedTypeNotAllowed.");
+ hrToOdExpectException(UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for unrecognized category. */
+ @Test
+ public void testUnrecognizedCategoryNotAllowed() throws Exception {
+ System.out.println("starting testUnrecognizedCategoryNotAllowed.");
+ hrToOdExpectException(UNRECOGNIZED_CATEGORY_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new DataCategoryFactory(), DATA_CATEGORY_HR_PATH, fileName);
+ }
+
+ private void testHrToOdDataCategory(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new DataCategoryFactory(),
+ DATA_CATEGORY_HR_PATH,
+ DATA_CATEGORY_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
new file mode 100644
index 000000000000..2be447e182b2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class DataLabelsTest {
+ private static final String DATA_LABELS_HR_PATH = "com/android/asllib/datalabels/hr";
+ private static final String DATA_LABELS_OD_PATH = "com/android/asllib/datalabels/od";
+
+ private static final String ACCESSED_VALID_BOOL_FILE_NAME =
+ "data-labels-accessed-valid-bool.xml";
+ private static final String ACCESSED_INVALID_BOOL_FILE_NAME =
+ "data-labels-accessed-invalid-bool.xml";
+ private static final String COLLECTED_VALID_BOOL_FILE_NAME =
+ "data-labels-collected-valid-bool.xml";
+ private static final String COLLECTED_INVALID_BOOL_FILE_NAME =
+ "data-labels-collected-invalid-bool.xml";
+ private static final String SHARED_VALID_BOOL_FILE_NAME = "data-labels-shared-valid-bool.xml";
+ private static final String SHARED_INVALID_BOOL_FILE_NAME =
+ "data-labels-shared-invalid-bool.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for data labels accessed valid bool. */
+ @Test
+ public void testDataLabelsAccessedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsAccessedValidBool.");
+ testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels accessed invalid bool. */
+ @Test
+ public void testDataLabelsAccessedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsAccessedInvalidBool.");
+ hrToOdExpectException(ACCESSED_INVALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels collected valid bool. */
+ @Test
+ public void testDataLabelsCollectedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsCollectedValidBool.");
+ testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels collected invalid bool. */
+ @Test
+ public void testDataLabelsCollectedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsCollectedInvalidBool.");
+ hrToOdExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels shared valid bool. */
+ @Test
+ public void testDataLabelsSharedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsSharedValidBool.");
+ testHrToOdDataLabels(SHARED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels shared invalid bool. */
+ @Test
+ public void testDataLabelsSharedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsSharedInvalidBool.");
+ hrToOdExpectException(SHARED_INVALID_BOOL_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
+ }
+
+ private void testHrToOdDataLabels(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc, new DataLabelsFactory(), DATA_LABELS_HR_PATH, DATA_LABELS_OD_PATH, fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
new file mode 100644
index 000000000000..ff8346a526ad
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class DeveloperInfoTest {
+ private static final String DEVELOPER_INFO_HR_PATH = "com/android/asllib/developerinfo/hr";
+ private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od";
+ public static final List<String> REQUIRED_FIELD_NAMES =
+ List.of("address", "countryRegion", "email", "name", "relationship");
+ public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId");
+
+ private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for all fields valid. */
+ @Test
+ public void testAllFieldsValid() throws Exception {
+ System.out.println("starting testAllFieldsValid.");
+ testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
+ }
+
+ /** Tests missing required fields fails. */
+ @Test
+ public void testMissingRequiredFields() throws Exception {
+ System.out.println("Starting testMissingRequiredFields");
+ for (String reqField : REQUIRED_FIELD_NAMES) {
+ System.out.println("testing missing required field: " + reqField);
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ developerInfoEle.get(0).removeAttribute(reqField);
+
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new DeveloperInfoFactory().createFromHrElements(developerInfoEle));
+ }
+ }
+
+ /** Tests missing optional fields passes. */
+ @Test
+ public void testMissingOptionalFields() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES) {
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ developerInfoEle.get(0).removeAttribute(optField);
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromHrElements(developerInfoEle);
+ developerInfo.toOdDomElements(mDoc);
+ }
+ }
+
+ private void testHrToOdDeveloperInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new DeveloperInfoFactory(),
+ DEVELOPER_INFO_HR_PATH,
+ DEVELOPER_INFO_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
new file mode 100644
index 000000000000..b62620ef417e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class SafetyLabelsTest {
+ private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr";
+ private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od";
+
+ private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for safety labels missing version. */
+ @Test
+ public void testSafetyLabelsMissingVersion() throws Exception {
+ System.out.println("starting testSafetyLabelsMissingVersion.");
+ hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ }
+
+ /** Test for safety labels valid empty. */
+ @Test
+ public void testSafetyLabelsValidEmptyFile() throws Exception {
+ System.out.println("starting testSafetyLabelsValidEmptyFile.");
+ testHrToOdSafetyLabels(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for safety labels with data labels. */
+ @Test
+ public void testSafetyLabelsWithDataLabels() throws Exception {
+ System.out.println("starting testSafetyLabelsWithDataLabels.");
+ testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
+ }
+
+ private void testHrToOdSafetyLabels(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new SafetyLabelsFactory(),
+ SAFETY_LABELS_HR_PATH,
+ SAFETY_LABELS_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
new file mode 100644
index 000000000000..191091a9e187
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class SystemAppSafetyLabelTest {
+ private static final String SYSTEM_APP_SAFETY_LABEL_HR_PATH =
+ "com/android/asllib/systemappsafetylabel/hr";
+ private static final String SYSTEM_APP_SAFETY_LABEL_OD_PATH =
+ "com/android/asllib/systemappsafetylabel/od";
+
+ private static final String VALID_FILE_NAME = "valid.xml";
+ private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for valid. */
+ @Test
+ public void testValid() throws Exception {
+ System.out.println("starting testValid.");
+ testHrToOdSystemAppSafetyLabel(VALID_FILE_NAME);
+ }
+
+ /** Tests missing url. */
+ @Test
+ public void testMissingUrl() throws Exception {
+ System.out.println("starting testMissingUrl.");
+ hrToOdExpectException(MISSING_URL_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(
+ new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName);
+ }
+
+ private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new SystemAppSafetyLabelFactory(),
+ SYSTEM_APP_SAFETY_LABEL_HR_PATH,
+ SYSTEM_APP_SAFETY_LABEL_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
new file mode 100644
index 000000000000..56503f7d6c6b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class TransparencyInfoTest {
+ private static final String TRANSPARENCY_INFO_HR_PATH =
+ "com/android/asllib/transparencyinfo/hr";
+ private static final String TRANSPARENCY_INFO_OD_PATH =
+ "com/android/asllib/transparencyinfo/od";
+
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_DEVELOPER_INFO_FILE_NAME = "with-developer-info.xml";
+ private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for transparency info valid empty. */
+ @Test
+ public void testTransparencyInfoValidEmptyFile() throws Exception {
+ System.out.println("starting testTransparencyInfoValidEmptyFile.");
+ testHrToOdTransparencyInfo(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for transparency info with developer info. */
+ @Test
+ public void testTransparencyInfoWithDeveloperInfo() throws Exception {
+ System.out.println("starting testTransparencyInfoWithDeveloperInfo.");
+ testHrToOdTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
+ }
+
+ /** Test for transparency info with app info. */
+ @Test
+ public void testTransparencyInfoWithAppInfo() throws Exception {
+ System.out.println("starting testTransparencyInfoWithAppInfo.");
+ testHrToOdTransparencyInfo(WITH_APP_INFO_FILE_NAME);
+ }
+
+ private void testHrToOdTransparencyInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new TransparencyInfoFactory(),
+ TRANSPARENCY_INFO_HR_PATH,
+ TRANSPARENCY_INFO_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
new file mode 100644
index 000000000000..faea340ae7bd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.marshallable.AslMarshallable;
+import com.android.asllib.marshallable.AslMarshallableFactory;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class TestUtils {
+ public static final String HOLDER_TAG_NAME = "holder_of_flattened_for_testing";
+
+ /** Reads a Resource file into a String. */
+ public static String readStrFromResource(Path filePath) throws IOException {
+ InputStream hrStream =
+ TestUtils.class.getClassLoader().getResourceAsStream(filePath.toString());
+ return new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
+ }
+
+ /** Gets List of Element from a path to an existing Resource. */
+ public static List<Element> getElementsFromResource(Path filePath)
+ throws ParserConfigurationException, IOException, SAXException {
+ String str = readStrFromResource(filePath);
+ InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+ Element root = document.getDocumentElement();
+ if (root.getTagName().equals(HOLDER_TAG_NAME)) {
+ String tagName =
+ XmlUtils.asElementList(root.getChildNodes()).stream()
+ .findFirst()
+ .get()
+ .getTagName();
+ return XmlUtils.asElementList(root.getElementsByTagName(tagName));
+ } else {
+ return List.of(root);
+ }
+ }
+
+ /** Reads a Document into a String. */
+ public static String docToStr(Document doc, boolean omitXmlDeclaration)
+ throws TransformerException {
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(
+ OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration ? "yes" : "no");
+
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ StreamResult streamResult = new StreamResult(outStream); // out
+ DOMSource domSource = new DOMSource(doc);
+ transformer.transform(domSource, streamResult);
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Gets formatted XML for slightly more robust comparison checking than naive string comparison.
+ */
+ public static String getFormattedXml(String xmlStr, boolean omitXmlDeclaration)
+ throws ParserConfigurationException, IOException, SAXException, TransformerException {
+ InputStream stream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+
+ return docToStr(document, omitXmlDeclaration);
+ }
+
+ /** Helper for getting a new Document */
+ public static Document document() throws ParserConfigurationException {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ }
+
+ /** Helper for testing human-readable to on-device conversion expecting exception */
+ public static <T extends AslMarshallable> void hrToOdExpectException(
+ AslMarshallableFactory<T> factory, String hrFolderPath, String fileName) {
+ assertThrows(
+ MalformedXmlException.class,
+ () -> {
+ factory.createFromHrElements(
+ TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+ });
+ }
+
+ /** Helper for testing human-readable to on-device conversion */
+ public static <T extends AslMarshallable> void testHrToOd(
+ Document doc,
+ AslMarshallableFactory<T> factory,
+ String hrFolderPath,
+ String odFolderPath,
+ String fileName)
+ throws Exception {
+ AslMarshallable marshallable =
+ factory.createFromHrElements(
+ TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+
+ for (var child : marshallable.toOdDomElements(doc)) {
+ doc.appendChild(child);
+ }
+ String converted = TestUtils.docToStr(doc, true);
+ System.out.println("converted: " + converted);
+
+ String expectedOdContents =
+ TestUtils.readStrFromResource(Paths.get(odFolderPath, fileName));
+ assertEquals(
+ TestUtils.getFormattedXml(expectedOdContents, true),
+ TestUtils.getFormattedXml(converted, true));
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
new file mode 100644
index 000000000000..ec0cd702fd43
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
@@ -0,0 +1,3 @@
+<app-metadata-bundles>
+
+</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
new file mode 100644
index 000000000000..19bfd826f770
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
@@ -0,0 +1 @@
+<app-metadata-bundles version="123456"></app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
new file mode 100644
index 000000000000..53794a1d1c80
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+ <safety-labels version="12345">
+ </safety-labels>
+</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
new file mode 100644
index 000000000000..7bcde4547933
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+<system-app-safety-label url="www.example.com">
+</system-app-safety-label>
+</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
new file mode 100644
index 000000000000..00bcfa80e9b1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+<transparency-info>
+</transparency-info>
+</app-metadata-bundles> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
new file mode 100644
index 000000000000..37bdfad4065f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
@@ -0,0 +1,3 @@
+<bundle>
+ <long name="version" value="123456"/>
+</bundle> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
new file mode 100644
index 000000000000..74644ed0413c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
@@ -0,0 +1,6 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ </pbundle_as_map>
+</bundle>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
new file mode 100644
index 000000000000..ef0f549fc46b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
@@ -0,0 +1,6 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+</bundle> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
new file mode 100644
index 000000000000..63c5094333cc
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
@@ -0,0 +1,4 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="transparency_info"/>
+</bundle> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
new file mode 100644
index 000000000000..883170a2d36f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
@@ -0,0 +1,14 @@
+<app-info
+ title="beervision"
+ description="a beer app"
+ containsAds="true"
+ obeyAps="false"
+ adsFingerprinting="false"
+ securityFingerprinting="false"
+ privacyPolicy="www.example.com"
+ securityEndpoints="url1|url2|url3"
+ firstPartyEndpoints="url1"
+ serviceProviderEndpoints="url55|url56"
+ category="Food and drink"
+ email="max@maxloh.com"
+ website="www.example.com" /> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
new file mode 100644
index 000000000000..6e976a3278de
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
@@ -0,0 +1,25 @@
+
+<pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoint" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoint" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoint" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ <string name="website" value="www.example.com"/>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..520e525679b8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml
@@ -0,0 +1,17 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_interaction"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="in_app_search_history"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="installed_apps"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_generated_content"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml
new file mode 100644
index 000000000000..0d08e5b5ae4d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="app_performance"
+ dataType="crash_logs"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="performance_diagnostics"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml
new file mode 100644
index 000000000000..b1cf3b44fd4a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="audio"
+ dataType="sound_recordings"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="music_files"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml
new file mode 100644
index 000000000000..a723070c43b7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="calendar"
+ dataType="calendar"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml
new file mode 100644
index 000000000000..2fe28ffe4940
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="contacts"
+ dataType="contacts"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml
new file mode 100644
index 000000000000..49a326fc43e5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="email_text_message"
+ dataType="emails"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="text_messages"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml
new file mode 100644
index 000000000000..f5de3707ef8a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml
@@ -0,0 +1,14 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="financial"
+ dataType="card_bank_account"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="purchase_history"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="credit_score"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml
new file mode 100644
index 000000000000..9891f8170bd7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="health_fitness"
+ dataType="health"
+ purposes="analytics" />
+ <data-shared dataCategory="health_fitness"
+ dataType="fitness"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml
new file mode 100644
index 000000000000..3e74da1ad527
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="identifiers"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml
new file mode 100644
index 000000000000..4762f16d64db
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ purposes="analytics" />
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml
new file mode 100644
index 000000000000..964e178e4dbd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml
new file mode 100644
index 000000000000..3ce1288fb2c3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml
@@ -0,0 +1,4 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="email_address" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml
new file mode 100644
index 000000000000..68baae30ef4f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml
new file mode 100644
index 000000000000..921a90a3fde7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="unrecognized"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml
new file mode 100644
index 000000000000..4533773ec9c1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml
@@ -0,0 +1,31 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ ephemeral="true"
+ isCollectionOptional="true"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="physical_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="phone_number"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="race_ethnicity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="political_or_religious_beliefs"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="sexual_orientation_or_gender_identity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="personal_identifiers"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml
new file mode 100644
index 000000000000..234fb265ae55
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="photo_video"
+ dataType="photos"
+ purposes="analytics" />
+ <data-shared dataCategory="photo_video"
+ dataType="videos"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..db851633aec5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="search_and_browsing"
+ dataType="web_browsing_history"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml
new file mode 100644
index 000000000000..9aad02de8877
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="storage"
+ dataType="files_docs"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml
new file mode 100644
index 000000000000..64b9ea72e05b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="unrecognized"
+ dataType="email_address"
+ purposes="analytics" />
+</holder_of_flattened_for_testing> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml
new file mode 100644
index 000000000000..5b99900b5a8a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml
@@ -0,0 +1,27 @@
+<pbundle_as_map name="actions_in_app">
+ <pbundle_as_map name="user_interaction">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="in_app_search_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="installed_apps">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="user_generated_content">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml
new file mode 100644
index 000000000000..0fe102202be5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="app_performance">
+ <pbundle_as_map name="crash_logs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="performance_diagnostics">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml
new file mode 100644
index 000000000000..51f1dfd6d823
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="audio">
+ <pbundle_as_map name="sound_recordings">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="music_files">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml
new file mode 100644
index 000000000000..326da47a1cb9
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="calendar">
+ <pbundle_as_map name="calendar">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml
new file mode 100644
index 000000000000..5d4387d2f906
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="contacts">
+ <pbundle_as_map name="contacts">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml
new file mode 100644
index 000000000000..5ac98f56ace6
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="email_text_message">
+ <pbundle_as_map name="emails">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="text_messages">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml
new file mode 100644
index 000000000000..a66f1a44dcb3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml
@@ -0,0 +1,22 @@
+<pbundle_as_map name="financial">
+ <pbundle_as_map name="card_bank_account">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="purchase_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="credit_score">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml
new file mode 100644
index 000000000000..8e697b47364c
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="health_fitness">
+ <pbundle_as_map name="health">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="fitness">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml
new file mode 100644
index 000000000000..34b4016e1364
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="identifiers">
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml
new file mode 100644
index 000000000000..db2e6965ff2a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml
new file mode 100644
index 000000000000..839922a8bec6
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml
new file mode 100644
index 000000000000..43650b6a77fe
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml
@@ -0,0 +1,50 @@
+<pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ <boolean name="is_collection_optional" value="true" />
+ <boolean name="ephemeral" value="true" />
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="physical_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="phone_number">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="race_ethnicity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="political_or_religious_beliefs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="sexual_orientation_or_gender_identity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="personal_identifiers">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml
new file mode 100644
index 000000000000..2a3178005df3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="photo_video">
+ <pbundle_as_map name="photos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="videos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml
new file mode 100644
index 000000000000..9e654efcc5ab
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="search_and_browsing">
+ <pbundle_as_map name="web_browsing_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml
new file mode 100644
index 000000000000..9abc37fdb2b9
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="storage">
+ <pbundle_as_map name="files_docs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
new file mode 100644
index 000000000000..bb45f426e083
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-accessed dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
new file mode 100644
index 000000000000..f927bba838fd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
@@ -0,0 +1,6 @@
+<data-labels>
+ <data-accessed dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
new file mode 100644
index 000000000000..ba11afbdce5f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-collected dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
new file mode 100644
index 000000000000..4b6d39776aeb
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-collected dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isCollectionOptional="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
new file mode 100644
index 000000000000..7840b9876ad8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isCollectionOptional="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
new file mode 100644
index 000000000000..ccf77b0e03be
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
new file mode 100644
index 000000000000..ddefc18f62e3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_accessed">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml
new file mode 100644
index 000000000000..252c7282da20
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_collected">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_collection_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
new file mode 100644
index 000000000000..d1d4e33e855a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml
new file mode 100644
index 000000000000..908d8ea2f53e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml
@@ -0,0 +1,8 @@
+<developer-info
+ name="max"
+ email="max@example.com"
+ address="111 blah lane"
+ countryRegion="US"
+ relationship="aosp"
+ website="example.com"
+ registryId="registry_id" /> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml
new file mode 100644
index 000000000000..784ec6156c1d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml
@@ -0,0 +1,9 @@
+<pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ <string name="app_developer_registry_id" value="registry_id"/>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
new file mode 100644
index 000000000000..762f3bdf7875
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
@@ -0,0 +1,2 @@
+<safety-labels>
+</safety-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
new file mode 100644
index 000000000000..7decfd4865b1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
@@ -0,0 +1 @@
+<safety-labels version="12345"></safety-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
new file mode 100644
index 000000000000..8997f4f30c33
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
@@ -0,0 +1,9 @@
+<safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ isSharingOptional="false"
+ ephemeral="false"
+ purposes="app_functionality" />
+ </data-labels>
+</safety-labels> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
new file mode 100644
index 000000000000..4f03d88a3bd2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
new file mode 100644
index 000000000000..a966fdaf9fe0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
new file mode 100644
index 000000000000..ff26c05abdb0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
@@ -0,0 +1 @@
+<system-app-safety-label></system-app-safety-label> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
new file mode 100644
index 000000000000..6fe86c33523b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
@@ -0,0 +1 @@
+<system-app-safety-label url="www.example.com"></system-app-safety-label> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
new file mode 100644
index 000000000000..f96535b4b49b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml
new file mode 100644
index 000000000000..254a37fb99e8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml
@@ -0,0 +1,4 @@
+
+<transparency-info>
+
+</transparency-info> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
new file mode 100644
index 000000000000..a7c48fc68cf1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
@@ -0,0 +1,4 @@
+
+<transparency-info>
+ <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+</transparency-info> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
new file mode 100644
index 000000000000..862bda465b25
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
@@ -0,0 +1,11 @@
+
+<transparency-info>
+ <developer-info
+ name="max"
+ email="max@example.com"
+ address="111 blah lane"
+ countryRegion="US"
+ relationship="aosp"
+ website="example.com"
+ appDeveloperRegistryId="registry_id" />
+</transparency-info> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml
new file mode 100644
index 000000000000..af574cf92b3a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml
@@ -0,0 +1 @@
+<pbundle_as_map name="transparency_info"/> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
new file mode 100644
index 000000000000..b813641f74f8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
@@ -0,0 +1,26 @@
+
+<pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoint" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoint" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoint" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
new file mode 100644
index 000000000000..101c98bd8e60
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
@@ -0,0 +1,11 @@
+
+<pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ </pbundle_as_map>
+</pbundle_as_map> \ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
index b2ff4495a6d2..b2ff4495a6d2 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
index 81277bf456a4..81277bf456a4 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
index ac844b3b2767..ac844b3b2767 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
index d0a3bfa7e64f..d0a3bfa7e64f 100644
--- a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 30333da5e86c..682adbc86d06 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -82,13 +82,30 @@ java_library {
jarjar_rules: "jarjar-rules.txt",
}
+// For sharing the code with other tools
+java_library_host {
+ name: "hoststubgen-lib",
+ defaults: ["ravenwood-internal-only-visibility-java"],
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "hoststubgen-helper-runtime",
+ ],
+ libs: [
+ "junit",
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "ow2-asm-util",
+ ],
+}
+
// Host-side stub generator tool.
java_binary_host {
name: "hoststubgen",
- main_class: "com.android.hoststubgen.Main",
- srcs: ["src/**/*.kt"],
+ main_class: "com.android.hoststubgen.HostStubGenMain",
static_libs: [
- "hoststubgen-helper-runtime",
+ "hoststubgen-lib",
"junit",
"ow2-asm",
"ow2-asm-analysis",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 1089f82b6472..803dc283b8c7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -32,7 +32,6 @@ import com.android.hoststubgen.visitors.PackageRedirectRemapper
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.FileOutputStream
@@ -52,7 +51,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val stats = HostStubGenStats()
// Load all classes.
- val allClasses = loadClassStructures(options.inJar.get)
+ val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
@@ -92,55 +91,6 @@ class HostStubGen(val options: HostStubGenOptions) {
}
/**
- * Load all the classes, without code.
- */
- private fun loadClassStructures(inJar: String): ClassNodes {
- log.i("Reading class structure from $inJar ...")
- val start = System.currentTimeMillis()
-
- val allClasses = ClassNodes()
-
- log.withIndent {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
-
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- if (entry.name.endsWith(".class")) {
- val cr = ClassReader(bis)
- val cn = ClassNode()
- cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES)
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file. It contains a *.dex file.")
- } else {
- // Unknown file type. Skip.
- while (bis.available() > 0) {
- bis.skip((1024 * 1024).toLong())
- }
- }
- }
- }
- }
- }
- if (allClasses.size == 0) {
- log.w("$inJar contains no *.class files.")
- }
-
- val end = System.currentTimeMillis()
- log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
- return allClasses
- }
-
- /**
* Build the filter, which decides what classes/methods/fields should be put in stub or impl
* jars, and "how". (e.g. with substitution?)
*/
@@ -229,7 +179,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val intersectingJars = mutableMapOf<String, ClassNodes>()
filenames.forEach { filename ->
- intersectingJars[filename] = loadClassStructures(filename)
+ intersectingJars[filename] = ClassNodes.loadClassStructures(filename)
}
return intersectingJars
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 4882c00d2b3c..45e7e301c0d1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:JvmName("Main")
+@file:JvmName("HostStubGenMain")
package com.android.hoststubgen
import java.io.PrintWriter
-const val COMMAND_NAME = "HostStubGen"
-
/**
* Entry point.
*/
fun main(args: Array<String>) {
+ executableName = "HostStubGen"
+
var success = false
var clanupOnError = false
@@ -33,7 +33,7 @@ fun main(args: Array<String>) {
val options = HostStubGenOptions.parseArgs(args)
clanupOnError = options.cleanUpOnError.get
- log.v("HostStubGen started")
+ log.v("$executableName started")
log.v("Options: $options")
// Run.
@@ -41,7 +41,7 @@ fun main(args: Array<String>) {
success = true
} catch (e: Throwable) {
- log.e("$COMMAND_NAME: Error: ${e.message}")
+ log.e("$executableName: Error: ${e.message}")
if (e !is UserErrorException) {
e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
}
@@ -49,7 +49,7 @@ fun main(args: Array<String>) {
TODO("Remove output jars here")
}
} finally {
- log.i("$COMMAND_NAME finished")
+ log.i("$executableName finished")
log.flush()
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9f5d524517d0..9ff798a4b5cb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -268,7 +268,7 @@ class HostStubGenOptions(
}
if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
- " $COMMAND_NAME will not generate jar files.")
+ " $executableName will not generate jar files.")
}
if (ret.enableNonStubMethodCallDetection.get) {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 937e56c2cbb5..aa63d8d9f870 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,11 @@
package com.android.hoststubgen
/**
+ * Name of this executable. Set it in the main method.
+ */
+var executableName = "[command name not set]"
+
+/**
* A regex that maches whitespate.
*/
val whitespaceRegex = """\s+""".toRegex()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 0579c2bb0525..83e122feeeb2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -34,6 +34,9 @@ val CLASS_INITIALIZER_NAME = "<clinit>"
/** Descriptor of the class initializer method. */
val CLASS_INITIALIZER_DESC = "()V"
+/** Name of constructors. */
+val CTOR_NAME = "<init>"
+
/**
* Find any of [anyAnnotations] from the list of visible / invisible annotations.
*/
@@ -135,10 +138,10 @@ fun writeByteCodeToPushArguments(
// Note, long and double will consume two local variable spaces, so the extra `i++`.
when (type) {
Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitVarInsn(Opcodes.ILOAD, i)
- Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
+ Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
else -> writer.visitVarInsn(Opcodes.ALOAD, i)
}
@@ -154,10 +157,10 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
// See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
when (type) {
Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitInsn(Opcodes.IRETURN)
- Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
+ Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
else -> writer.visitInsn(Opcodes.ARETURN)
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index bc34ef0dc8a7..92906a75b93a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -16,13 +16,18 @@
package com.android.hoststubgen.asm
import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.InvalidJarFileException
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TypeAnnotationNode
+import java.io.BufferedInputStream
import java.io.PrintWriter
import java.util.Arrays
+import java.util.zip.ZipFile
/**
* Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -62,8 +67,8 @@ class ClassNodes {
/** Find a field, which may not exist. */
fun findField(
- className: String,
- fieldName: String,
+ className: String,
+ fieldName: String,
): FieldNode? {
return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
return fn
@@ -72,14 +77,14 @@ class ClassNodes {
/** Find a method, which may not exist. */
fun findMethod(
- className: String,
- methodName: String,
- descriptor: String,
+ className: String,
+ methodName: String,
+ descriptor: String,
): MethodNode? {
return findClass(className)?.methods
- ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
- return mn
- }
+ ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+ return mn
+ }
}
/** @return true if a class has a class initializer. */
@@ -106,26 +111,33 @@ class ClassNodes {
private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
- dumpAnnotations(pw, " ",
- cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
- cn.visibleAnnotations, cn.invisibleAnnotations,
- )
+ dumpAnnotations(
+ pw, " ",
+ cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
+ cn.visibleAnnotations, cn.invisibleAnnotations,
+ )
for (f in cn.fields ?: emptyList()) {
- pw.printf(" Field: %s [sig: %s] [desc: %s] [access: %x]\n",
- f.name, f.signature, f.desc, f.access)
- dumpAnnotations(pw, " ",
- f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
- f.visibleAnnotations, f.invisibleAnnotations,
- )
+ pw.printf(
+ " Field: %s [sig: %s] [desc: %s] [access: %x]\n",
+ f.name, f.signature, f.desc, f.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
+ f.visibleAnnotations, f.invisibleAnnotations,
+ )
}
for (m in cn.methods ?: emptyList()) {
- pw.printf(" Method: %s [sig: %s] [desc: %s] [access: %x]\n",
- m.name, m.signature, m.desc, m.access)
- dumpAnnotations(pw, " ",
- m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
- m.visibleAnnotations, m.invisibleAnnotations,
- )
+ pw.printf(
+ " Method: %s [sig: %s] [desc: %s] [access: %x]\n",
+ m.name, m.signature, m.desc, m.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
+ m.visibleAnnotations, m.invisibleAnnotations,
+ )
}
}
@@ -136,7 +148,7 @@ class ClassNodes {
invisibleTypeAnnotations: List<TypeAnnotationNode>?,
visibleAnnotations: List<AnnotationNode>?,
invisibleAnnotations: List<AnnotationNode>?,
- ) {
+ ) {
for (an in visibleTypeAnnotations ?: emptyList()) {
pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
}
@@ -166,4 +178,55 @@ class ClassNodes {
}
}
}
+
+ companion object {
+ /**
+ * Load all the classes, without code.
+ */
+ fun loadClassStructures(inJar: String): ClassNodes {
+ log.i("Reading class structure from $inJar ...")
+ val start = System.currentTimeMillis()
+
+ val allClasses = ClassNodes()
+
+ log.withIndent {
+ ZipFile(inJar).use { inZip ->
+ val inEntries = inZip.entries()
+
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
+
+ BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ if (entry.name.endsWith(".class")) {
+ val cr = ClassReader(bis)
+ val cn = ClassNode()
+ cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES)
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
+ }
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$inJar is not a desktop jar file. It contains a *.dex file.")
+ } else {
+ // Unknown file type. Skip.
+ while (bis.available() > 0) {
+ bis.skip((1024 * 1024).toLong())
+ }
+ }
+ }
+ }
+ }
+ }
+ if (allClasses.size == 0) {
+ log.w("$inJar contains no *.class files.")
+ }
+
+ val end = System.currentTimeMillis()
+ log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+ return allClasses
+ }
+ }
} \ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 78b13fd36f06..5a26fc69d473 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -19,14 +19,14 @@ import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.HostStubGenInternalException
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
-import com.android.hoststubgen.asm.isAnonymousInnerClass
-import com.android.hoststubgen.log
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.isAnnotation
+import com.android.hoststubgen.asm.isAnonymousInnerClass
import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
import com.android.hoststubgen.asm.isEnum
import com.android.hoststubgen.asm.isSynthetic
import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.log
import org.objectweb.asm.tree.ClassNode
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index f70a17d9b7cd..fa8fe6cd384f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1833,7 +1833,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 2
+ interfaces: 0, fields: 1, methods: 11, attributes: 2
int value;
descriptor: I
flags: (0x0000)
@@ -1938,6 +1938,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
x: athrow
LineNumberTable:
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
}
SourceFile: "TinyFrameworkNative.java"
RuntimeInvisibleAnnotations:
@@ -1955,7 +1959,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 2
+ interfaces: 0, fields: 0, methods: 5, attributes: 2
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2013,6 +2017,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
Start Length Slot Name Signature
0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
0 7 1 arg I
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 arg1 B
+ 0 5 1 arg2 B
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index c9c607c58c68..11d5939b7917 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -2236,7 +2236,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 3
+ interfaces: 0, fields: 1, methods: 11, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -2435,6 +2435,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -2457,7 +2474,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 3
+ interfaces: 0, fields: 0, methods: 5, attributes: 3
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2551,6 +2568,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 15 5 0 arg1 B
+ 15 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index a57907d9398b..088bc80e11c5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2743,7 +2743,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 11, attributes: 3
+ interfaces: 0, fields: 1, methods: 12, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -3002,6 +3002,28 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -3024,7 +3046,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 3
+ interfaces: 0, fields: 0, methods: 6, attributes: 3
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3148,6 +3170,36 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 26 5 0 arg1 B
+ 26 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index 5a5e22db59e5..09ee183a2dcc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -52,4 +52,6 @@ public class TinyFrameworkNative {
public static void nativeStillNotSupported_should_be_like_this() {
throw new RuntimeException();
}
+
+ public static native byte nativeBytePlus(byte arg1, byte arg2);
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
index 749ebaa378e3..b23c21602967 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -34,4 +34,8 @@ public class TinyFrameworkNative_host {
public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) {
return source.value + arg;
}
+
+ public static byte nativeBytePlus(byte arg1, byte arg2) {
+ return (byte) (arg1 + arg2);
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index ba17c75132f2..762180dcf74b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -154,13 +154,22 @@ public class TinyFrameworkClassTest {
}
@Test
+ public void testNativeSubstitutionLong() {
+ assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L);
+ }
+
+ @Test
+ public void testNativeSubstitutionByte() {
+ assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7);
+ }
+
+ @Test
public void testNativeSubstitutionClass_nonStatic() {
TinyFrameworkNative instance = new TinyFrameworkNative();
instance.setValue(5);
assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
}
-
@Test
public void testSubstituteNativeWithThrow() throws Exception {
// We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 837dae92b711..0f1373c34ce6 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,20 @@ import com.github.javaparser.ParseProblemException
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.ArrayAccessExpr
+import com.github.javaparser.ast.expr.ArrayCreationExpr
+import com.github.javaparser.ast.expr.ArrayInitializerExpr
+import com.github.javaparser.ast.expr.AssignExpr
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.IntegerLiteralExpr
import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.MethodReferenceExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
import com.github.javaparser.ast.expr.ObjectCreationExpr
@@ -168,6 +177,8 @@ object ProtoLogTool {
val classNameNode = classDeclaration.findFirst(SimpleName::class.java).get()
classNameNode.setId(protoLogImplGenName)
+ injectCacheClass(classDeclaration, groups, protoLogGroupsClassName)
+
injectConstants(classDeclaration,
viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
protoLogGroupsClassName)
@@ -238,6 +249,12 @@ object ProtoLogTool {
field.setFinal(true)
field.variables.first().setInitializer(treeMapCreation)
}
+ ProtoLogToolInjected.Value.CACHE_UPDATER.name -> {
+ field.setFinal(true)
+ field.variables.first().setInitializer(MethodReferenceExpr()
+ .setScope(NameExpr("Cache"))
+ .setIdentifier("update"))
+ }
else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
}
}
@@ -245,6 +262,61 @@ object ProtoLogTool {
}
}
+ private fun injectCacheClass(
+ classDeclaration: ClassOrInterfaceDeclaration,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String,
+ ) {
+ val cacheClass = ClassOrInterfaceDeclaration()
+ .setName("Cache")
+ .setPublic(true)
+ .setStatic(true)
+ for (group in groups) {
+ val nodeList = NodeList<Expression>()
+ val defaultVal = BooleanLiteralExpr(group.value.textEnabled || group.value.enabled)
+ repeat(LogLevel.entries.size) { nodeList.add(defaultVal) }
+ cacheClass.addFieldWithInitializer(
+ "boolean[]",
+ "${group.key}_enabled",
+ ArrayCreationExpr().setElementType("boolean[]").setInitializer(
+ ArrayInitializerExpr().setValues(nodeList)
+ ),
+ Modifier.Keyword.PUBLIC,
+ Modifier.Keyword.STATIC
+ )
+ }
+
+ val updateBlockStmt = BlockStmt()
+ for (group in groups) {
+ for (level in LogLevel.entries) {
+ updateBlockStmt.addStatement(
+ AssignExpr()
+ .setTarget(
+ ArrayAccessExpr()
+ .setName(NameExpr("${group.key}_enabled"))
+ .setIndex(IntegerLiteralExpr(level.ordinal))
+ ).setValue(
+ MethodCallExpr()
+ .setName("isEnabled")
+ .setArguments(NodeList(
+ FieldAccessExpr()
+ .setScope(NameExpr(protoLogGroupsClassName))
+ .setName(group.value.name),
+ FieldAccessExpr()
+ .setScope(NameExpr("LogLevel"))
+ .setName(level.toString()),
+ ))
+ )
+ )
+ }
+ }
+
+ cacheClass.addMethod("update").setPrivate(true).setStatic(true)
+ .setBody(updateBlockStmt)
+
+ classDeclaration.addMember(cacheClass)
+ }
+
private fun tryParse(code: String, fileName: String): CompilationUnit {
try {
return StaticJavaParser.parse(code)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 2b7164191dd0..6a8a0717b2f1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -22,6 +22,7 @@ import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.ArrayAccessExpr
import com.github.javaparser.ast.expr.CastExpr
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
@@ -35,6 +36,8 @@ import com.github.javaparser.ast.expr.TypeExpr
import com.github.javaparser.ast.expr.VariableDeclarationExpr
import com.github.javaparser.ast.stmt.BlockStmt
import com.github.javaparser.ast.stmt.ExpressionStmt
+import com.github.javaparser.ast.stmt.IfStmt
+import com.github.javaparser.ast.stmt.Statement
import com.github.javaparser.ast.type.ArrayType
import com.github.javaparser.ast.type.ClassOrInterfaceType
import com.github.javaparser.ast.type.PrimitiveType
@@ -74,6 +77,8 @@ class SourceTransformer(
private val protoLogImplClassNode =
StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+ private val protoLogImplCacheClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>("$protoLogImplClassName.Cache")
private var processedCode: MutableList<String> = mutableListOf()
private var offsets: IntArray = IntArray(0)
/** The path of the file being processed, relative to $ANDROID_BUILD_TOP */
@@ -121,8 +126,9 @@ class SourceTransformer(
group: LogGroup,
level: LogLevel,
messageString: String
- ): BlockStmt {
+ ): Statement {
val hash = CodeUtils.hash(packagePath, messageString, level, group)
+
val newCall = call.clone()
if (!group.textEnabled) {
// Remove message string if text logging is not enabled by default.
@@ -166,11 +172,15 @@ class SourceTransformer(
}
blockStmt.addStatement(ExpressionStmt(newCall))
- return blockStmt
+ val isLogEnabled = ArrayAccessExpr()
+ .setName(NameExpr("$protoLogImplCacheClassNode.${group.name}_enabled"))
+ .setIndex(IntegerLiteralExpr(level.ordinal))
+
+ return IfStmt(isLogEnabled, blockStmt, null)
}
private fun injectProcessedCallStatementInCode(
- processedCallStatement: BlockStmt,
+ processedCallStatement: Statement,
parentStmt: ExpressionStmt
) {
// Inline the new statement.
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index de0b5bae118e..82aa93da613b 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -76,7 +76,7 @@ class SourceTransformerTest {
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -86,7 +86,7 @@ class SourceTransformerTest {
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -98,8 +98,8 @@ class SourceTransformerTest {
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -109,7 +109,7 @@ class SourceTransformerTest {
class Test {
void test() {
- { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -119,7 +119,7 @@ class SourceTransformerTest {
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -129,7 +129,7 @@ class SourceTransformerTest {
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
}
}